diff --git a/.github/workflows/bump-api-enumerations.yml b/.github/workflows/bump-api-enumerations.yml
new file mode 100644
index 0000000..b0fa42d
--- /dev/null
+++ b/.github/workflows/bump-api-enumerations.yml
@@ -0,0 +1,48 @@
+name: Bump api-enumerations submodule
+
+on:
+ schedule:
+ # 03:00 UTC on the 1st of every month.
+ - cron: "0 3 1 * *"
+ workflow_dispatch: {}
+
+jobs:
+ bump:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: recursive
+ token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Update api-enumerations submodule
+ id: update
+ run: |
+ cd external/api-enumerations
+ git fetch origin
+ before=$(git rev-parse HEAD)
+ git checkout origin/master
+ after=$(git rev-parse HEAD)
+ cd ../..
+ if [ "$before" = "$after" ]; then
+ echo "changed=false" >> "$GITHUB_OUTPUT"
+ else
+ echo "changed=true" >> "$GITHUB_OUTPUT"
+ fi
+
+ - name: Create pull request
+ if: steps.update.outputs.changed == 'true'
+ uses: peter-evans/create-pull-request@v6
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ commit-message: "Bump api-enumerations submodule"
+ title: "Bump api-enumerations submodule"
+ body: |
+ Automated monthly update of the `external/api-enumerations` submodule
+ to the latest upstream `master` commit.
+
+ Review the upstream diff before merging — new/changed enum
+ descriptions will flow into the generated value types (plan `04`)
+ the next time the package is built and released.
+ branch: chore/bump-api-enumerations
+ delete-branch: true
diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml
index bb92a10..c4cc2af 100644
--- a/.github/workflows/continuous-integration-workflow.yml
+++ b/.github/workflows/continuous-integration-workflow.yml
@@ -1,21 +1,73 @@
name: Continuous Integration Workflow
-on: [push, pull_request]
+on:
+ push:
+ pull_request:
+ workflow_dispatch:
+ inputs:
+ version:
+ description: "NuGet package version (SemVer, e.g. 9.1.0-pre.1)"
+ required: true
+ type: string
+ publish_package:
+ description: "Publish packages and create a release"
+ required: true
+ default: true
+ type: boolean
+ prerelease:
+ description: "Mark the GitHub release as prerelease"
+ required: true
+ default: true
+ type: boolean
jobs:
build:
runs-on: ubuntu-latest
env:
- VERSION: 8.0.${{ github.run_number }}
+ VERSION: ${{ github.ref == 'refs/heads/master' && 'v9.0.0' || (github.event_name == 'workflow_dispatch' && format('v{0}', github.event.inputs.version) || format('v9.0.0-pre{0}', github.run_number)) }}
+ PACKAGE_VERSION: ${{ github.ref == 'refs/heads/master' && '9.0.0' || (github.event_name == 'workflow_dispatch' && github.event.inputs.version || format('9.0.0-pre{0}', github.run_number)) }}
+ PUBLISH_PACKAGE: ${{ (github.event_name == 'workflow_dispatch' && github.event.inputs.publish_package == 'true') || github.ref == 'refs/heads/master' || github.ref == 'refs/heads/prerelease' }}
+ IS_PRERELEASE: ${{ (github.event_name == 'workflow_dispatch' && github.event.inputs.prerelease == 'true') || github.ref != 'refs/heads/master' }}
DOCKER_BUILDKIT: 1
BUILDKIT_PROGRESS: plain
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
+ with:
+ submodules: recursive
- name: Setup .NET
- uses: actions/setup-dotnet@v1
+ uses: actions/setup-dotnet@v4
+ with:
+ global-json-file: global.json
- name: Docker Build NuGet packages
+ env:
+ COMPANIES_HOUSE_API_KEY: ${{ secrets.COMPANIES_HOUSE_API_KEY }}
+ run: |
+ docker build --build-arg NUGET_PACKAGE_VERSION=${{ env.PACKAGE_VERSION }} --secret id=companies_house_api_key,env=COMPANIES_HOUSE_API_KEY -f ./Dockerfile --output ./ .
+ - name: Validate NuGet package metadata
run: |
- docker build --build-arg NUGET_PACKAGE_VERSION=${{ env.VERSION }} --build-arg COMPANIES_HOUSE_API_KEY=${{ secrets.COMPANIES_HOUSE_API_KEY }} -f ./Dockerfile --output ./ .
+ mapfile -t packages < <(find ./artifacts -maxdepth 1 -type f -name '*.nupkg' ! -name '*.snupkg' | sort)
+ if [ "${#packages[@]}" -eq 0 ]; then
+ echo "No .nupkg files were produced."
+ exit 1
+ fi
+
+ for package in "${packages[@]}"; do
+ if ! unzip -Z1 "$package" | grep -qx "README.md"; then
+ echo "README.md missing from package: $package"
+ exit 1
+ fi
+
+ nuspec_path="$(unzip -Z1 "$package" | grep -E '\.nuspec$' | head -n 1)"
+ if [ -z "$nuspec_path" ]; then
+ echo "No .nuspec file found in package: $package"
+ exit 1
+ fi
+
+ if ! unzip -p "$package" "$nuspec_path" | grep -q 'README.md'; then
+ echo "NuSpec readme metadata missing from package: $package"
+ exit 1
+ fi
+ done
- name: Publish Unit Test Results
uses: dorny/test-reporter@v1
if: always()
@@ -26,19 +78,40 @@ jobs:
fail-on-error: true
fail-on-empty: true
- name: NuGet.Org push
- if: github.ref == 'refs/heads/master'
+ if: ${{ env.PUBLISH_PACKAGE }}
+ run: |
+ dotnet nuget push ./artifacts/*.nupkg --source NuGet.org --api-key ${{ secrets.NUGET_API_KEY }} --skip-duplicate
+ - name: Generate release notes with NuGet links
+ if: ${{ env.PUBLISH_PACKAGE }}
run: |
- dotnet nuget push ./artifacts/*.nupkg --source NuGet.org --api-key ${{ secrets.NUGET_API_KEY }}
+ printf '%s\n' \
+ '## NuGet Packages' \
+ '' \
+ 'This release includes the following NuGet packages:' \
+ '' \
+ "- [CompaniesHouse](https://www.nuget.org/packages/CompaniesHouse/${{ env.PACKAGE_VERSION }}) - Core .NET client for Companies House API" \
+ "- [CompaniesHouse.Extensions.Microsoft.DependencyInjection](https://www.nuget.org/packages/CompaniesHouse.Extensions.Microsoft.DependencyInjection/${{ env.PACKAGE_VERSION }}) - DI helpers for ASP.NET Core / generic-host apps" \
+ '' \
+ 'Install via:' \
+ '```' \
+ 'dotnet add package CompaniesHouse' \
+ 'dotnet add package CompaniesHouse.Extensions.Microsoft.DependencyInjection' \
+ '```' \
+ '' \
+ 'See the [README](https://github.com/kevbite/CompaniesHouse.NET#readme) for usage instructions.' \
+ > release_notes.md
- name: Create Release
id: create_release
- if: github.ref == 'refs/heads/master'
- uses: actions/create-release@v1
+ if: ${{ env.PUBLISH_PACKAGE }}
+ uses: softprops/action-gh-release@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ env.VERSION }}
- release_name: Release ${{ env.VERSION }}
- body: |
- Release ${{ env.VERSION }}
+ name: Release ${{ env.VERSION }}
+ body_path: release_notes.md
+ files: |
+ ./artifacts/*.nupkg
+ ./artifacts/*.snupkg
draft: false
- prerelease: false
+ prerelease: ${{ env.IS_PRERELEASE == 'true' }}
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..4d8e671
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "external/api-enumerations"]
+ path = external/api-enumerations
+ url = https://github.com/companieshouse/api-enumerations
diff --git a/.plans/README.md b/.plans/README.md
new file mode 100644
index 0000000..33c67d3
--- /dev/null
+++ b/.plans/README.md
@@ -0,0 +1,44 @@
+# .plans
+
+The work breakdown for the **CompaniesHouse.NET v-next** major rewrite (on the
+`prerelease` branch).
+
+## How this folder works
+
+- **`outstanding/`** — plans not yet completed. Each file is a self-contained,
+ refinable unit of work. Numeric prefixes suggest ordering (lower first).
+- **`completed/`** — plans that have been fully delivered and verified. When you
+ finish a plan, **move its file here** in the same change.
+
+Plans are **living documents**. Refine tasks, record decisions, and capture
+open questions as you learn. A plan is "done" only when its acceptance criteria
+are met and its code is merged to `prerelease`.
+
+## Plan index (outstanding)
+
+| # | Plan | Theme |
+|---|------|-------|
+| 00 | `00-foundation-solution-and-build.md` | `.slnx`, central packages, multi-target net8/9/10, drop Newtonsoft, CI |
+| 01 | `01-core-client-architecture.md` | `CompaniesHouseClient` entry point, sub-client pattern, `System.Text.Json`, response/error model |
+| 02 | `02-di-extensions-ioptions.md` | Modern DI with `IOptions<>` / `AddOptions` / config binding |
+| 03 | `03-string-backed-value-types.md` | Replace all enums with string-backed `readonly record struct`s |
+| 04 | `04-enum-source-generator.md` | Roslyn generator that emits the value types |
+| 05 | `05-api-enumerations-submodule.md` | `api-enumerations` git submodule + local "extra" lists |
+| 06 | `06-endpoint-search.md` | All 7 search endpoints (incl. advanced search) — **start here for endpoints** |
+| 07 | `07-endpoint-company-profile.md` | Company profile |
+| 08 | `08-endpoint-officers.md` | Officer list + get appointment |
+| 09 | `09-endpoint-catalogue-remaining.md` | Every other endpoint, to be split into its own plan when picked up |
+| 10 | `10-testing-strategy.md` | Unit / scenario / integration / generator tests |
+| 11 | `11-docs-samples-migration.md` | README, samples, v-old → v-next migration guide |
+| 99 | `99-recurring-issues-backlog.md` | Historical pain points the design must eliminate |
+
+## Suggested execution order
+
+1. **Foundation** (`00`) — get the solution building on modern targets first.
+2. **Core + DI + enums** (`01`, `02`, `03`, `04`, `05`) — the plumbing every
+ endpoint depends on. `03`/`04`/`05` can proceed in parallel with `01`.
+3. **Endpoints, one at a time** (`06` → `07` → `08` → `09`), starting with
+ search. Each endpoint should be shippable on its own.
+4. **Testing and docs** (`10`, `11`) run continuously alongside the endpoints.
+
+Keep `99` open as a checklist to validate the design against real-world bugs.
diff --git a/.plans/completed/.gitkeep b/.plans/completed/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/.plans/completed/00-foundation-solution-and-build.md b/.plans/completed/00-foundation-solution-and-build.md
new file mode 100644
index 0000000..5625451
--- /dev/null
+++ b/.plans/completed/00-foundation-solution-and-build.md
@@ -0,0 +1,158 @@
+# 00 — Foundation: solution, packaging & build
+
+**Status:** complete
+**Depends on:** nothing (do this first)
+**Blocks:** everything
+
+## Goal
+
+Get the repository building on a modern, consistent foundation so every
+subsequent plan lands on solid ground: modern target frameworks, central
+package management, `.slnx` solution, and an updated CI pipeline. No behaviour
+changes to the client itself — this is pure infrastructure.
+
+## Why
+
+The current projects target `netstandard1.1;netstandard2.0;net45`, pin package
+versions per-project, use a classic `.sln`, and depend on `Newtonsoft.Json`.
+For a clean-slate major version we want the most modern setup possible.
+
+## Scope
+
+### Target frameworks
+- Multi-target the shippable libraries to **`net8.0;net9.0;net10.0`**.
+ - `src/CompaniesHouse`
+ - `src/CompaniesHouse.Extensions.Microsoft.DependencyInjection`
+- Remove `netstandard*` / `net45` targets and the
+ `Microsoft.NETFramework.ReferenceAssemblies` and `Microsoft.Net.Http`
+ package references.
+- Tests target `net8.0;net9.0;net10.0` (or just `net10.0` if multi-targeting
+ tests is not worth the run time — decide and note it).
+- The source generator project (plan `04`) targets **`netstandard2.0`** — this
+ is a hard Roslyn requirement and is the one exception to the "no netstandard"
+ rule.
+
+### Central Package Management (CPM)
+- Add a root **`Directory.Packages.props`** with
+ `true` and a
+ `` for every dependency used anywhere in the repo.
+- Strip `Version="..."` from every `` in every `.csproj`.
+- Consolidate versions so all projects share one version per package
+ (previously `Microsoft.Extensions.*` was pinned to `3.1.9`).
+
+### Directory.Build.props / .targets
+- Enable **`enable`** (already have `ImplicitUsings`,
+ `LangVersion latest`, `TreatWarningsAsErrors`, `EnforceCodeStyleInBuild`).
+- Refresh `` (currently hard-coded to 2020) — use a year-agnostic or
+ current value.
+- Keep `IncludeSymbols` + `snupkg`; add ``,
+ ``, `` and
+ **deterministic builds** for good source-link/NuGet hygiene.
+- Add `true` on the
+ shipped libraries so public XML docs are packaged.
+
+### Solution format
+- Convert `CompaniesHouse.sln` to **`CompaniesHouse.slnx`** (the new XML
+ solution format). Verify `dotnet build CompaniesHouse.slnx` works with the
+ installed SDK (repo has 10.x and 11.x preview SDKs available). Delete the old
+ `.sln` once the `.slnx` is proven, or keep both briefly if tooling needs it —
+ decide and note.
+- Add an `.slnx` entry for the future source-generator project.
+
+### CI workflow
+- Update `.github/workflows/continuous-integration-workflow.yml`:
+ - Ensure the SDK it installs can build `net10.0` (+ `.slnx`); pin via
+ `global.json` if needed.
+ - Recursively checkout submodules (needed once plan `05` lands):
+ `actions/checkout` with `submodules: recursive`.
+ - Bump the `VERSION` scheme to the new major (the prerelease tag currently
+ produces `9.0.0-preN` — align with the chosen next major).
+ - Modernise action versions (`checkout@v2`/`setup-dotnet@v1` are old).
+
+## Tasks
+
+- [x] Add `Directory.Packages.props` and migrate all `PackageReference`s.
+- [x] Retarget both library projects to `net8.0;net9.0;net10.0`.
+- [x] Remove framework-reference/`Microsoft.Net.Http` packages.
+- [x] Enable nullable + doc generation + deterministic build in `Directory.Build.props`.
+- [x] Convert solution to `.slnx`; add all existing projects.
+- [x] Update CI (SDK, submodules, versioning, action versions).
+- [x] `dotnet build -c Release` and `dotnet test -c Release` are green.
+- [x] Migrate test stack from NUnit/FluentAssertions to xUnit/Shouldly (scope
+ addition requested mid-execution — FluentAssertions' license changed to a
+ paid tier from v8; NUnit swapped along with it). See "Test stack
+ migration" below.
+
+## Design decisions
+
+- **CPM over per-project versions** — single place to bump, no drift.
+- **Drop `netstandard`** — the new major only supports in-support .NET; this
+ is an intentional breaking change and is fine for a new major.
+
+## Open questions
+
+- Do we keep `net8.0` (LTS) as the floor, or go `net9.0`+ only? (Assumption:
+ keep `net8.0` for the widest supported reach; revisit if a dependency forces
+ it.)
+Keep net8.0 for the time being.
+
+- Should tests multi-target or run once on `net10.0`? (Assumption: run on
+ `net10.0` only for speed; multi-target the libraries only.)
+Just target the latest version of `net10.0`
+
+## Acceptance criteria
+
+- Solution builds and tests pass from a clean checkout with only the .NET SDK
+ installed.
+- No `Newtonsoft.Json`, `netstandard`, or `net45` remain in any shipped
+ project (Newtonsoft removal itself is finished in plan `01`).
+- All package versions resolve from `Directory.Packages.props`.
+
+## Test stack migration (Shouldly + xUnit)
+
+Mid-execution the user asked to drop FluentAssertions (license changed to a
+paid tier from v8) in favour of **Shouldly**, and to swap **NUnit for xUnit**
+at the same time. This expanded plan `00`'s scope to a full test-framework
+port across all four test projects (~66 files). Completed:
+
+- `Directory.Packages.props`: removed `NUnit`, `NUnit3TestAdapter`,
+ `FluentAssertions`; added `xunit` (2.9.2), `xunit.runner.visualstudio`
+ (2.8.2), `Shouldly` (4.2.1).
+- All NUnit attributes converted to xUnit: `[TestFixture]` removed,
+ `[Test]` → `[Fact]`/`[Theory]`, `[TestCase]` → `[InlineData]`,
+ `[TestCaseSource]` → `[MemberData]`, `[SetUp]`/`[TearDown]` → constructor
+ or `IAsyncLifetime`.
+- All FluentAssertions/`NUnit` classic assertions converted to Shouldly
+ (`.Should().Be(x)` → `.ShouldBe(x)`, etc).
+- **Enum-equivalency redesign**: `CompaniesHouse.Tests` had bespoke
+ FluentAssertions `IEquivalencyStep` classes (`ComparingEnumWith`,
+ `ComparingArrayEnumWith`) registered via a `[SetUpFixture]` (`Initializer`)
+ to bridge test-fixture raw wire strings against real deserialized C# enum
+ properties during `BeEquivalentTo` comparisons. Shouldly has no equivalent
+ extensibility point. Replaced with a single dependency-free helper,
+ `tests/CompaniesHouse.Tests/EquivalencyAssertionExtensions.cs`, exposing
+ `actual.ShouldBeEquivalentTo(expected, params string[] excludingPropertyNames)`.
+ It recursively walks public properties and bridges enum ↔ raw wire string
+ automatically via each enum member's `[EnumMember(Value=...)]` attribute (no
+ per-enum registration needed — a strict improvement over the old
+ `MapProviders` dictionaries, which had to be hand-maintained in parallel
+ with the enums). The old `ComparingEnumWith.cs`, `ComparingArrayEnumWith.cs`
+ and `Initializer.cs` were deleted. `EnumerationMappings.cs`/`MapProviders/*`
+ were kept — they're still used to enumerate wire-string values for
+ parameterized `TestCaseSource`/`MemberData` test data.
+- Result: `CompaniesHouse.slnx` builds with 0 errors; 594/597 tests pass. The
+ 3 failures (`OfficersTestsInvalid`, `PersonsWithSignificantControlTestsInValid`,
+ `CompanyFilingHistoryTestsInvalid` in `CompaniesHouse.IntegrationTests`) are
+ pre-existing/unrelated to this migration — the live Companies House API now
+ returns `200` with an empty result set for malformed company numbers instead
+ of `404`, so the "invalid number ⇒ null data" assumption in these three
+ tests is stale against current API behaviour. Not fixed here (out of scope
+ for infrastructure plan `00`); worth a follow-up ticket.
+- `AGENTS.md` and `.plans/outstanding/10-testing-strategy.md` updated to
+ document xUnit + Shouldly as the standing test-stack convention.
+
+## References
+
+- Issue #188 (System.Text.Json), #199/#191 (move to GitHub Actions — already
+ done, keep modern).
+- `.slnx` format: current .NET SDK solution tooling.
diff --git a/.plans/completed/01-core-client-architecture.md b/.plans/completed/01-core-client-architecture.md
new file mode 100644
index 0000000..6a4f895
--- /dev/null
+++ b/.plans/completed/01-core-client-architecture.md
@@ -0,0 +1,120 @@
+# 01 — Core client architecture
+
+**Status:** complete
+**Depends on:** `00-foundation`
+**Blocks:** all endpoint plans (`06`+)
+
+## Goal
+
+Establish the core client plumbing for the new major version: the
+`CompaniesHouseClient` facade, the per-capability sub-client pattern,
+`System.Text.Json` serialization, a redesigned response wrapper, and consistent
+error handling. This is the skeleton every endpoint hangs off.
+
+## Why
+
+The old client wires up ~11 sub-clients by hand in a constructor, uses
+`Newtonsoft.Json`, and returns a bare `CompaniesHouseClientResponse` that
+carries only `Data`. Consumers have repeatedly asked for richer responses
+(status code, headers, retry-after) and the serialization stack must move to
+STJ.
+
+## Scope
+
+### Entry point & sub-client pattern (keep the shape)
+- `CompaniesHouseClient : ICompaniesHouseClient` remains the single entry point.
+- Every capability is its own sub-client behind its own interface
+ (`ICompaniesHouseSearchClient`, `ICompaniesHouseCompanyProfileClient`, ...),
+ aggregated by `ICompaniesHouseClient`. Preserve this — it is a deliberate,
+ liked design.
+- Keep the two construction paths:
+ - `new CompaniesHouseClient(HttpClient)` — bring-your-own `HttpClient`
+ (the DI/`IHttpClientFactory` path).
+ - `new CompaniesHouseClient(settings)` — convenience path that builds an
+ `HttpClient` with the auth handler.
+- Keep the small **URI builder** types per endpoint; they are unit-testable and
+ already proven. New endpoints follow the same pattern.
+
+### Serialization: System.Text.Json
+- Central `JsonSerializerOptions` factory used by every sub-client:
+ - `PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower` (API is
+ snake_case) — verify member-by-member with `[JsonPropertyName]` where the
+ policy is insufficient.
+ - Register the string-backed value-type converters (plan `03`).
+ - Custom converters for CH's quirky date formats (`yyyy-MM-dd` and partial
+ dates like month/year only — see old `OptionalDateJsonConverter`).
+ - `NumberHandling = AllowReadingFromString` where the API returns numbers as
+ strings (issue #212: `total_results`/`items_per_page`/`start_index` came
+ back as strings for SearchAll).
+- Prefer **`System.Text.Json` source-generation** (`JsonSerializerContext`) for
+ the response models to stay trim/AOT-friendly and fast. Decide whether to
+ require this for all models or adopt incrementally.
+- Delete every `Newtonsoft.Json`-based converter under `JsonConverters/` and
+ reimplement the needed ones for STJ.
+
+### Response & error model (redesign — issues #181, #182, #189)
+- Redesign the response wrapper so callers can see transport metadata, not just
+ the body. Candidate shape:
+ ```csharp
+ public sealed class CompaniesHouseClientResponse
+ {
+ public T? Data { get; }
+ public int StatusCode { get; }
+ public string? ReasonPhrase { get; }
+ public TimeSpan? RetryAfter { get; } // 429 handling (#181/#182)
+ public bool IsSuccess { get; }
+ // headers exposed read-only
+ }
+ ```
+- Define the semantics for **not found**: today several `GetX` methods return
+ `null` data on 404. Decide between "null data + `IsSuccess=false`" vs a
+ dedicated result type, and document it. (Assumption: keep returning a
+ response with `Data == null` for 404 on single-resource gets, but surface
+ `StatusCode`.)
+- Replace `EnsureSuccessStatusCode2()` with explicit handling that captures the
+ status/headers/retry-after before throwing (or before returning a non-success
+ response). Define the exception type(s) for genuine errors.
+
+### Auth & settings
+- Keep `IApiKeyProvider` abstraction (issues #192/#194 want the key pulled from
+ arbitrary locations) — the DI layer (plan `02`) supplies implementations.
+- Keep `CompaniesHouseAuthorizationHandler` (Basic auth: API key as username).
+- Confirm the base URI: the old default is `https://api.companieshouse.gov.uk/`
+ but the current spec host is `https://api.company-information.service.gov.uk/`
+ — **update the default** and note it as a breaking change.
+
+## Tasks
+
+- [ ] Add the STJ `JsonSerializerOptions` factory + `JsonSerializerContext`.
+- [ ] Port/replace date and number converters for STJ.
+- [ ] Redesign `CompaniesHouseClientResponse` (status/headers/retry-after).
+- [ ] Rework the HTTP send/deserialize pipeline shared by sub-clients.
+- [ ] Update the default base URI to the current CH host.
+- [ ] Remove all `Newtonsoft.Json` usage from `src/CompaniesHouse`.
+- [ ] Keep `CompaniesHouseClient` + sub-client interfaces as the public shape.
+
+## Design decisions
+
+- **Sub-client-per-capability** preserved — good separation, already liked.
+- **STJ everywhere**, ideally source-generated — perf + trimming/AOT.
+- **Richer response wrapper** — directly resolves long-standing requests.
+
+## Open questions
+
+- One `JsonSerializerContext` for the whole assembly, or per-endpoint? (Lean:
+ one shared context.)
+- Do we keep throwing on non-2xx, or always return a response and let callers
+ branch on `IsSuccess`? (Lean: return response for expected 404s; throw for
+ unexpected 5xx/transport — finalise here.)
+
+## Acceptance criteria
+
+- A trivial call (e.g. company profile) round-trips via STJ with no Newtonsoft.
+- Response exposes status code + retry-after on both success and failure.
+- All sub-clients share one serialization + send pipeline.
+
+## References
+
+- Issues #181/#182 (headers/retry-after), #189 (response redesign), #188 (STJ),
+ #212 (numbers-as-strings), #156 (raw values), #202 (auth specifics).
+- API host: `api.company-information.service.gov.uk` (see `swagger.json`).
diff --git a/.plans/completed/02-di-extensions-ioptions.md b/.plans/completed/02-di-extensions-ioptions.md
new file mode 100644
index 0000000..e496b25
--- /dev/null
+++ b/.plans/completed/02-di-extensions-ioptions.md
@@ -0,0 +1,94 @@
+# 02 — DI extensions with IOptions<>
+
+**Status:** complete
+**Depends on:** `01-core-client-architecture`
+**Blocks:** nothing (parallel with endpoints)
+
+## Goal
+
+Modernise `CompaniesHouse.Extensions.Microsoft.DependencyInjection` to use the
+`IOptions<>` pattern (`AddOptions`, `IConfiguration` binding, validation) and
+provide clean overloads to configure the client several ways.
+
+## Why
+
+The current extension stashes a `CompaniesHouseClientOptions` as a singleton it
+news-up by hand, rather than using the framework's options pipeline. It also
+targets `netstandard2.0` and pins `Microsoft.Extensions.*` to `3.1.9`. A new
+major is the time to adopt the idiomatic options approach and let consumers bind
+from configuration and validate on start.
+
+## Scope
+
+### Options type
+- Define `CompaniesHouseClientOptions` (name/section e.g. `"CompaniesHouse"`)
+ with at least `BaseUri`, `ApiKey`, and room for future knobs (timeouts,
+ document API base URI). Add **`DataAnnotations`** (`[Required]` ApiKey, valid
+ `Uri`) for validation.
+- Provide a defaulted `BaseUri` (current CH host — see plan `01`).
+
+### Registration via the options pipeline
+- Use `services.AddOptions()` with:
+ - `.Bind(configuration.GetSection("CompaniesHouse"))`
+ - `.Configure(...)` for the delegate overloads
+ - `.ValidateDataAnnotations()`
+ - `.ValidateOnStart()`
+- Register the client as a **typed `HttpClient`**
+ (`AddHttpClient`) and set
+ `BaseAddress` from resolved options.
+- Keep `services.TryAdd*` for all sub-client interfaces resolving from the
+ single `ICompaniesHouseClient` (issue #190 — libraries should use `TryAdd`;
+ already done, preserve it).
+- Register `IApiKeyProvider` (default `StaticApiKeyProvider` from options),
+ overridable by consumers (issues #192/#194).
+
+### Overloads (multiple ways to configure)
+Provide these entry points:
+1. `AddCompaniesHouseClient(string apiKey)`
+2. `AddCompaniesHouseClient(Uri baseUri, string apiKey)`
+3. `AddCompaniesHouseClient(Action configure)`
+4. `AddCompaniesHouseClient(Action configure)`
+5. `AddCompaniesHouseClient(IConfiguration section)` /
+ `AddCompaniesHouseClient(IConfiguration config, string sectionName = "CompaniesHouse")`
+6. A named/keyed variant so multiple configured clients can coexist (optional —
+ note if deferred).
+- Allow the caller to customise the underlying `IHttpClientBuilder` (return it,
+ or accept a `Action`), so Polly/resilience handlers can be
+ added.
+
+### Packaging
+- Retarget to `net8.0;net9.0;net10.0`, versions via `Directory.Packages.props`.
+- Bump `Microsoft.Extensions.*` to versions matching the target frameworks.
+
+## Tasks
+
+- [ ] Add DataAnnotations validation to `CompaniesHouseClientOptions`.
+- [ ] Rewrite registration around `AddOptions` + `ValidateOnStart`.
+- [ ] Add the `IConfiguration`-binding overloads.
+- [ ] Keep `TryAdd` sub-client registrations; add new sub-clients as endpoints land.
+- [ ] Return `IHttpClientBuilder` (or expose a hook) for resilience config.
+- [ ] Update DI tests (plan `10`) for the new surface.
+
+## Design decisions
+
+- **`IOptions<>` + `ValidateOnStart`** — fail fast on a missing/invalid API key
+ instead of at first request.
+- **Config binding** — first-class `appsettings.json` support.
+
+## Open questions
+
+- Do we ship keyed/named multi-client support in v-next or defer? (Assumption:
+ design the API so it can be added later; defer the actual keyed overloads
+ unless cheap.)
+- Section name default: `"CompaniesHouse"` — confirm.
+
+## Acceptance criteria
+
+- `services.AddCompaniesHouseClient(Configuration.GetSection("CompaniesHouse"))`
+ binds and validates; a missing API key fails at startup.
+- All previously-registered sub-client interfaces still resolve.
+- No `Newtonsoft.Json`; builds on all target frameworks.
+
+## References
+
+- Issues #190 (TryAdd), #192/#194 (API key from anywhere), #193 (TryAdd PR).
diff --git a/.plans/completed/03-string-backed-value-types.md b/.plans/completed/03-string-backed-value-types.md
new file mode 100644
index 0000000..d785b70
--- /dev/null
+++ b/.plans/completed/03-string-backed-value-types.md
@@ -0,0 +1,116 @@
+# 03 — String-backed value types (replace all enums)
+
+**Status:** complete
+**Depends on:** `01-core-client-architecture` (for STJ options)
+**Blocks:** every model with an "enum" field; pairs with `04` (generator)
+
+## Goal
+
+Replace every C# `enum` used on the API contract with a **string-backed
+`readonly record struct`** that preserves the raw wire value and never throws on
+an unrecognised value. This kills the single largest category of historical bugs
+in this library.
+
+## Why
+
+Companies House claim API versioning but do not honour it, so new string values
+appear in responses without warning. With plain enums + `StringEnumConverter`,
+each new value is a `JsonException`/`ArgumentException` at deserialization for
+every consumer who hasn't upgraded. See the design blog:
+ and the trail of
+issues: #168, #185, #186, #197, #198, #200, #201, #209, #218.
+
+## The pattern
+
+For each API enum group (e.g. `CompanyStatus`, `CompanyType`, `OfficerRole`):
+
+```csharp
+[JsonConverter(typeof(CompanyStatusJsonConverter))]
+public readonly record struct CompanyStatus(string Value)
+{
+ // Known values (generated — see plan 04)
+ public static CompanyStatus Active => new("active");
+ public static CompanyStatus Dissolved => new("dissolved");
+ // ...
+
+ public bool IsKnown => KnownValues.Contains(Value);
+
+ // Optional: human-readable description from api-enumerations
+ public string? Description => Descriptions.GetValueOrDefault(Value);
+
+ public override string ToString() => Value;
+}
+```
+
+Converter (trivial — just reads/writes the string, no lookup, no throw):
+
+```csharp
+public sealed class CompanyStatusJsonConverter : JsonConverter
+{
+ public override CompanyStatus Read(ref Utf8JsonReader reader, Type t, JsonSerializerOptions o)
+ => new(reader.GetString()!);
+ public override void Write(Utf8JsonWriter writer, CompanyStatus value, JsonSerializerOptions o)
+ => writer.WriteStringValue(value.Value);
+}
+```
+
+### Requirements
+- **Never throws** on unknown values — the raw string is retained (issue #156
+ asked for raw-value access; this delivers it for free).
+- **Equatable / usable in `switch`** via `== Known.X` patterns; value-equality
+ from `record struct`.
+- **`IsKnown`** to branch on recognised vs unrecognised.
+- **Prefix helpers** where CH uses structured values (the blog's
+ `IsProcessing`/`ProcessingStep` idea) — e.g. filing categories/subcategories.
+ Provide these where the enumeration is naturally hierarchical.
+- **Null handling** — the old `OptionalStringEnumConverter` mapped null to a
+ default. Decide: default to `default(struct)` (empty `Value`) vs a `None`
+ static. (Lean: `Value == ""`/`default` represents absent; expose `HasValue`.)
+- Optional **`Description`** property backed by the api-enumerations
+ descriptions, so consumers get the friendly text (partially covers issue #205
+ "SIC codes?" and the various `*_descriptions.yml`).
+
+## Scope
+
+- Define the shared building blocks (base converter helpers, common members,
+ analyzers/format) that the generator (plan `04`) will emit against.
+- Hand-author **one or two** value types first (e.g. `CompanyStatus`) to prove
+ the pattern and the converter, its tests, and STJ registration — then let the
+ generator take over producing the rest.
+- Registration: value-type converters are applied via `[JsonConverter]` on the
+ type, so no central registration is strictly needed, but confirm they compose
+ with the shared `JsonSerializerOptions` (plan `01`).
+
+## Tasks
+
+- [ ] Implement the reference value type + converter by hand (`CompanyStatus`).
+- [ ] Unit tests: known value, unknown value (no throw, raw preserved),
+ round-trip, equality, `IsKnown`, null/empty.
+- [ ] Decide + document null/absent semantics and prefix-helper conventions.
+- [ ] Freeze the shape the generator must emit (feed into plan `04`).
+- [ ] Migrate models to use value types as endpoints are built.
+
+## Design decisions
+
+- **`readonly record struct` wrapping a string** — preserves raw value, value
+ equality, cheap, immutable. Chosen over "enum + Unknown fallback" (loses the
+ raw value) per the blog.
+- **Converter does no validation** — unknown values are first-class, not errors.
+
+## Open questions
+
+- Provide implicit `string` conversions? (Lean: explicit `Value`/`ToString`
+ only, to avoid accidental stringly-typed misuse; revisit.)
+- Ship `Description` in v-next or defer? (Lean: ship it — it's cheap once the
+ generator reads the YAML descriptions and answers real requests.)
+
+## Acceptance criteria
+
+- Deserializing an unknown status string succeeds and round-trips byte-for-byte.
+- Known values compare equal to the static members.
+- No plain enum remains on any wire-facing model once endpoints are migrated.
+
+## References
+
+- Blog:
+- Issues #156, #168, #185, #186, #197, #198, #200, #201, #209, #218.
diff --git a/.plans/completed/04-enum-source-generator.md b/.plans/completed/04-enum-source-generator.md
new file mode 100644
index 0000000..ab5149a
--- /dev/null
+++ b/.plans/completed/04-enum-source-generator.md
@@ -0,0 +1,124 @@
+# 04 — Enum source generator
+
+**Status:** complete
+**Depends on:** `03-string-backed-value-types` (target shape), `05-submodule`
+(input data)
+**Blocks:** full model coverage
+
+## Goal
+
+Build a Roslyn **incremental source generator** that emits the string-backed
+value types (plan `03`) from the Companies House `api-enumerations` YAML plus our
+own local "extra" lists — so new enum values are picked up by rebuilding and
+releasing, never by hand-coding.
+
+## Why
+
+Hand-maintaining enum members is exactly the treadmill that produced issues
+#168, #185, #186, #197, #198, #200, #201, #209, #218. Generating from the
+authoritative YAML means a version bump (not a code change) absorbs new values,
+and unknown values never break consumers anyway (plan `03`).
+
+## Input data
+
+The `api-enumerations` repo (submodule — plan `05`) contains YAML files whose
+top-level keys are enum groups and whose entries are `'wire-value': "Friendly
+Description"`. Example (`constants.yml`):
+
+```yaml
+company_status:
+ 'active' : "Active"
+ 'dissolved' : "Dissolved"
+company_type:
+ 'ltd' : "Private limited company"
+ 'plc' : "Public limited company"
+```
+
+Relevant files include (non-exhaustive — enumerate at implementation time):
+`constants.yml` (company_status, company_type, company_summary, jurisdiction,
+identification_type, ...), `filing_history_descriptions.yml`,
+`mortgage_descriptions.yml`, `psc_descriptions.yml`,
+`disqualified_officer_descriptions.yml`, `exemption_descriptions.yml`,
+`officer_filing.yml`, `psc_filing.yml`, etc.
+
+## Design
+
+### Generator project
+- New project `src/CompaniesHouse.SourceGenerator`, targeting
+ **`netstandard2.0`** (Roslyn requirement), referencing
+ `Microsoft.CodeAnalysis.CSharp` (analyzer/generator packaging — `PrivateAssets`
+ so it isn't a runtime dependency of consumers).
+- Ship the generator **inside the `CompaniesHouse` package** (analyzer asset),
+ or wired as a project-reference `OutputItemType="Analyzer"` — decide packaging
+ (lean: bundle as analyzer in the main package so no extra dependency for
+ consumers).
+
+### Inputs → generator
+- Feed the YAML files as **`AdditionalFiles`** (from the submodule path + our
+ local extras folder) so the generator reads them via
+ `context.AdditionalTextsProvider` (incremental, cache-friendly). Avoid doing
+ network I/O in the generator — the submodule provides the files at build time.
+- A small **mapping/config** (attribute, or a `enum-map.json`) declares which
+ YAML key maps to which C# type name + namespace, plus:
+ - PascalCase member-name conversion from wire values
+ (`private-unlimited` → `PrivateUnlimited`), with a collision/override table
+ for awkward values (empty string, values differing only by punctuation, or
+ C# keyword clashes).
+ - Which groups get prefix helpers (plan `03`).
+ - Which groups expose `Description`.
+
+### Output
+- For each configured group, emit the `readonly record struct`, its `[JsonConverter]`,
+ the static known-value members, `KnownValues`, optional `Descriptions`
+ dictionary, `IsKnown`, and any configured prefix helpers — matching the frozen
+ shape from plan `03`.
+- Emit into the `CompaniesHouse.Response` (or a dedicated `CompaniesHouse.Enums`)
+ namespace.
+
+### Extensibility (our own extra lists)
+- Support a repo-local `enumerations/extra/*.yml` (same format) merged on top of
+ the submodule data, so we can add values CH hasn't published yet or define
+ library-only groups. Merge order: submodule first, extras override/append.
+
+## Tasks
+
+- [ ] Scaffold the generator project (netstandard2.0 + CodeAnalysis).
+- [ ] YAML parsing (a lightweight parser or `YamlDotNet` — note: generator deps
+ must be bundled into the analyzer; prefer a minimal parser to avoid load
+ issues).
+- [ ] Wire YAML files as `AdditionalFiles` (submodule + extras).
+- [ ] Implement wire-value → PascalCase with an override table.
+- [ ] Emit value types matching plan `03`'s shape.
+- [ ] Snapshot/verify tests over the generated output (plan `10`).
+- [ ] Package the generator as an analyzer in the `CompaniesHouse` package.
+
+## Design decisions
+
+- **Incremental generator + `AdditionalFiles`** — no network at build, cacheable,
+ fast.
+- **Local extras override submodule** — lets us react even faster than CH.
+- **Bundle in the main package** — zero extra dependency for consumers.
+
+## Open questions
+
+- YAML parser choice inside the generator (bundling `YamlDotNet` into an
+ analyzer can be fiddly). (Lean: minimal hand-rolled parser for the simple
+ `key: {'v':"desc"}` shape; revisit if files use richer YAML.)
+- Do we generate at consumer build-time, or generate once in *this* repo and
+ commit the output? (Lean: generate in *this* repo's build so the shipped
+ package contains concrete types; the generator need not run in consumers.
+ Confirm — this affects packaging: generator could be a build-time-only tool
+ rather than a shipped analyzer.)
+
+## Acceptance criteria
+
+- Adding a value to a YAML file and rebuilding produces a new static member with
+ no hand-editing.
+- Generated types compile clean under `TreatWarningsAsErrors`.
+- Unknown values (not in YAML) still deserialize fine at runtime (plan `03`).
+
+## References
+
+- api-enumerations:
+- Blog:
+- Recurring enum issues: #168, #185, #186, #197, #198, #200, #201, #209, #218.
diff --git a/.plans/completed/05-api-enumerations-submodule.md b/.plans/completed/05-api-enumerations-submodule.md
new file mode 100644
index 0000000..5f71edf
--- /dev/null
+++ b/.plans/completed/05-api-enumerations-submodule.md
@@ -0,0 +1,85 @@
+# 05 — api-enumerations submodule & local extras
+
+**Status:** complete
+**Depends on:** `00-foundation` (CI submodule checkout)
+**Blocks:** `04-enum-source-generator` (provides its input)
+
+## Goal
+
+Pull the Companies House `api-enumerations` data into the repo as a **git
+submodule** and establish a repo-local "extras" area, so the source generator
+(plan `04`) has a versioned, updatable source of enum values plus a place for our
+own additions.
+
+## Why
+
+The enum values must come from an authoritative, refreshable source rather than
+being copied into the repo by hand. A submodule pins an exact commit (reproducible
+builds) while making updates a one-liner. We also need a way to add values CH
+hasn't published yet, so we keep a local overlay.
+
+## Scope
+
+### Submodule
+- Add `https://github.com/companieshouse/api-enumerations` as a submodule at a
+ stable path, e.g. `external/api-enumerations`.
+ ```
+ git submodule add https://github.com/companieshouse/api-enumerations external/api-enumerations
+ ```
+- Pin to a known-good commit; document the update procedure:
+ ```
+ git submodule update --remote external/api-enumerations
+ ```
+- Ensure CI checks out submodules recursively (coordinated in plan `00`:
+ `actions/checkout` with `submodules: recursive`). The `Dockerfile` build path
+ must also receive the submodule content (copy it into the build context).
+
+### Local extras overlay
+- Create `enumerations/extra/` in this repo for our own YAML lists in the same
+ `key: {'value': "Description"}` format. Two uses:
+ 1. **Overrides/additions** to existing groups (values CH is late publishing).
+ 2. **Library-only groups** not present upstream.
+- Document the merge rule (submodule first, extras override/append) — consumed
+ by plan `04`.
+
+### Consumption
+- The generator reads YAML from **both** `external/api-enumerations/*.yml` and
+ `enumerations/extra/*.yml` via `AdditionalFiles` globs in the generator/host
+ project.
+
+## Tasks
+
+- [ ] Add the submodule at `external/api-enumerations` and pin a commit.
+- [ ] Add `.gitmodules`; verify a fresh `git clone --recursive` populates it.
+- [ ] Create `enumerations/extra/` with a README describing the format + merge
+ rules and a small example file.
+- [ ] Ensure CI and the Dockerfile build include submodule content.
+- [ ] Document the "how to refresh enumerations" steps (in the extras README or
+ AGENTS.md).
+
+## Design decisions
+
+- **Submodule over vendoring/copy** — pins an exact upstream commit, trivially
+ updatable, keeps provenance clear.
+- **Local overlay** — lets us out-run CH's publishing cadence without forking.
+
+## Open questions
+
+- Submodule path: `external/api-enumerations` vs `lib/` vs `third_party/`.
+ (Lean: `external/`.)
+- Auto-update cadence: a scheduled CI job that bumps the submodule and opens a
+ PR? (Nice-to-have; note as a follow-up, not required for v-next.)
+
+## Acceptance criteria
+
+- Fresh `git clone --recursive` yields the YAML files on disk.
+- CI builds have the submodule content available to the generator.
+- Adding a file under `enumerations/extra/` is picked up by the generator
+ (verified once plan `04` lands).
+
+## References
+
+- Enumerations repo:
+- Files seen: `constants.yml`, `filing_history_descriptions.yml`,
+ `mortgage_descriptions.yml`, `psc_descriptions.yml`, `officer_filing.yml`,
+ `disqualified_officer_descriptions.yml`, `exemption_descriptions.yml`, etc.
diff --git a/.plans/completed/06-endpoint-search.md b/.plans/completed/06-endpoint-search.md
new file mode 100644
index 0000000..8f7dd58
--- /dev/null
+++ b/.plans/completed/06-endpoint-search.md
@@ -0,0 +1,140 @@
+# 06 — Endpoint: Search (start here)
+
+**Status:** complete
+**Depends on:** `01-core`, `03-value-types` (for status/type fields)
+**Blocks:** nothing; first endpoint to build
+
+## Goal
+
+Build the full **Search** surface — the most-used part of the API — end to end
+against the current documentation, using the URI-builder pattern. This is the
+first endpoint rebuilt from scratch in v-next and sets the template for the rest.
+
+## Endpoints to cover
+
+From the API reference (verify exact paths/params against the live docs):
+
+| Method on client | Docs page | Path (verify) |
+|---|---|---|
+| `SearchAllAsync` | search-all | `GET /search` |
+| `SearchCompaniesAsync` | search-companies | `GET /search/companies` |
+| `SearchOfficersAsync` | search-officers | `GET /search/officers` |
+| `SearchDisqualifiedOfficersAsync` | search-disqualified-officers | `GET /search/disqualified-officers` |
+| `SearchCompaniesAlphabeticallyAsync` | search-companies-alphabetically | `GET /alphabetical-search/companies` |
+| `SearchDissolvedCompaniesAsync` | search-dissolved-companies | `GET /dissolved-search/companies` |
+| `AdvancedCompanySearchAsync` | advanced-company-search | `GET /advanced-search/companies` |
+
+Reference docs:
+-
+-
+-
+-
+-
+-
+-
+
+## Scope
+
+### URI builders (keep the pattern)
+- Preserve the `SearchUriBuilder`/factory approach: a base builder for the common
+ `q` / `items_per_page` / `start_index` query params, with per-search subclasses
+ adding their own params.
+- **Advanced search** (issue #216 "not implemented", #220 PR) has a rich set of
+ filter params (company name includes/excludes, company status, company type,
+ company subtype, dissolved-from/to, incorporated-from/to, SIC codes, location,
+ size). Model these as a dedicated request with a builder that emits only the
+ supplied params.
+- **Company search** carries a `restrictions` query param — the old code had a
+ bug (`if (string.IsNullOrWhiteSpace(...))` added it only when *empty*). Fix:
+ add `restrictions` only when **non**-empty (issues #203/#204/#208).
+
+### Request models
+- One request record per search (`SearchAllRequest`, `SearchCompaniesRequest`,
+ `AdvancedCompanySearchRequest`, ...). Use the string-backed value types for
+ `company_status`/`company_type`/`company_subtype` filters (plan `03`).
+
+### Response models
+- Model each response faithfully from the docs: the search envelope
+ (`total_results`, `items_per_page`, `start_index`, `page_number`, `kind`,
+ `items[]`) plus per-search item shapes.
+- **Numbers-as-strings**: SearchAll returned `total_results` etc. as strings —
+ handle with `NumberHandling.AllowReadingFromString` or a converter (issue #212).
+- Item enum-ish fields (company status/type, officer role, etc.) use value types.
+- The old code had a polymorphic `SearchItemConverter` for the "all" search
+ (mixed item kinds keyed by `kind`) — reimplement for STJ if `search/all`
+ returns heterogeneous items.
+
+### Client wiring
+- `ICompaniesHouseSearchClient` (+ granular interfaces if we keep the
+ per-search-interface split) hung off `CompaniesHouseClient`. Register in DI
+ (plan `02`).
+
+## Tasks
+
+- [ ] Confirm each path + full query-param list from the live docs.
+- [ ] Build request models (with value-type filters).
+- [ ] Build/extend URI builders per search; fix the `restrictions` bug.
+- [ ] Build response envelope + item models from the docs.
+- [ ] Handle numbers-as-strings and any polymorphic items.
+- [ ] Wire sub-client + DI registrations.
+- [ ] Tests: URI-builder unit tests, deserialization scenario tests, one
+ integration test per search (plan `10`).
+
+## Open questions
+
+- Do we keep separate `ICompaniesHouseSearchCompanyClient` etc. interfaces, or
+ collapse into one `ICompaniesHouseSearchClient` with all methods? (Lean: one
+ cohesive search sub-client interface; note the breaking change.)
+- Advanced search param names — confirm exact spelling from docs.
+
+## Acceptance criteria
+
+- All 7 searches callable from `CompaniesHouseClient`, returning typed results.
+- Unknown status/type values in results don't throw (value types).
+- `restrictions` is only sent when provided.
+
+## References
+
+- Issues #203/#204/#208 (restrictions), #212 (numeric strings), #216/#220
+ (advanced search), #185/#186 (new company statuses/types in search results).
+
+## Delivered
+
+- Fixed the long-standing `restrictions` query bug in
+ `SearchCompanyUriBuilder`: the parameter is now emitted only when a
+ non-empty value is supplied, it is URL-escaped consistently with the base `q`
+ handling, and `SearchCompanyRequest.Restrictions` is now nullable to reflect
+ the documented optional contract.
+- Added the three missing Search endpoints to `CompaniesHouseClient` and DI:
+ `SearchCompaniesAlphabeticallyAsync` (`GET /alphabetical-search/companies`),
+ `SearchDissolvedCompaniesAsync` (`GET /dissolved-search/companies`) and
+ `AdvancedCompanySearchAsync` (`GET /advanced-search/companies`), each with a
+ dedicated request model, URI builder, response envelope and item models wired
+ through the existing `CompaniesHouseSearchClient` / search-builder factory
+ pattern.
+- Modelled the new endpoint-specific query contracts from the live docs rather
+ than forcing them into the older `q/items_per_page/start_index` shape:
+ alphabetical search uses `search_above` / `search_below` / `size`,
+ dissolved search adds `search_type` plus its paging variants, and advanced
+ search emits only the supplied filters, formatting list filters as
+ comma-delimited query values and dates as `yyyy-MM-dd`.
+- Migrated `CompanyType` from the hand-written wire enum to the Roslyn
+ generator by adding `company_type` and `company_subtype` entries to
+ `enum-map.txt`, deleting the old `Response/CompanyType.cs`, and consuming the
+ generated string-backed `CompanyType` / `CompanySubtype` value types in
+ search/company-profile models and advanced-search filters. This keeps unknown
+ type/subtype values non-breaking in the same way `CompanyStatus` already is.
+- Added unit/integration coverage for the new surface: URI-builder tests for
+ the restrictions fix plus the new builders, search-client deserialization
+ tests for the 3 new endpoints, value-type round-trip tests for generated
+ `CompanyType` / `CompanySubtype`, DI resolution coverage for the new granular
+ interfaces, and new real-API integration tests for alphabetical, dissolved
+ and advanced company search.
+- Verified: full solution build (`CompaniesHouse.slnx`, Release) with 0 errors;
+ `CompaniesHouse.Tests` passing; `CompaniesHouse.ScenarioTests` passing;
+ `CompaniesHouse.SourceGenerator.Tests` 28/28 after adding a regression test
+ for multiple enum-map entries; `CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests`
+ passing; `dotnet format --verify-no-changes` clean on all touched files. The
+ full integration suite still has unrelated pre-existing failures in older
+ invalid-case tests, but the 3 new search integration tests pass when run
+ directly against a configured API key.
diff --git a/.plans/completed/07-endpoint-company-profile.md b/.plans/completed/07-endpoint-company-profile.md
new file mode 100644
index 0000000..2d50b8c
--- /dev/null
+++ b/.plans/completed/07-endpoint-company-profile.md
@@ -0,0 +1,100 @@
+# 07 — Endpoint: Company Profile
+
+**Status:** complete
+**Depends on:** `01-core`, `03-value-types`; do after `06-search`
+**Blocks:** nothing
+
+## Goal
+
+Rebuild the **Company Profile** endpoint from the current documentation.
+
+Docs:
+
+
+`GET /company/{companyNumber}`
+
+## Scope
+
+### Client
+- `ICompaniesHouseCompanyProfileClient.GetCompanyProfileAsync(string companyNumber, CancellationToken)`
+ hung off `CompaniesHouseClient`.
+- Keep the `CompanyProfileUriBuilder` pattern (`company/{escaped-number}`).
+- 404 semantics per plan `01` (response with `Data == null`, `StatusCode` set).
+
+### Response model (faithful to docs)
+Model the full profile, notably the historically-missing/tricky bits:
+- `company_status` / `company_status_detail` / `company_type` / `subtype` /
+ `jurisdiction` — **string-backed value types** (plan `03`) — these are the
+ exact fields that produced #184/#185/#186/#200/#214.
+- `registered_office_address`, `accounts` (incl. `accounting_reference_date`,
+ next/last made-up-to, overdue flags), `confirmation_statement`,
+ `annual_return`, `sic_codes` (issue #205), `previous_company_names`,
+ `foreign_company_details` (issue #217), `links`, `branch_company_details`,
+ `date_of_creation`/`date_of_cessation`, `has_charges`, `has_insolvency_history`,
+ `has_super_secure_pscs`, `registered_office_is_in_dispute`,
+ `undeliverable_registered_office_address`, `can_file`, `is_community_interest_company`.
+- Partial/February-style dates and month/year-only fields — use the shared date
+ converters (plan `01`).
+
+## Tasks
+
+- [ ] Confirm the full response schema from the docs.
+- [ ] Build the response model with value-type enums + `foreign_company_details`
+ + `sic_codes`.
+- [ ] Wire sub-client + DI registration.
+- [ ] Tests: URI builder, deserialization of a real sample payload, integration
+ test for a known company number.
+
+## Open questions
+
+- Does `accounting_reference_date` etc. come back as `{day, month}` objects?
+ Model as nested types — confirm from docs.
+
+## Acceptance criteria
+
+- A real company profile deserializes fully, including `foreign_company_details`
+ and `sic_codes`.
+- Unknown `company_status`/`company_type` values do not throw.
+
+## References
+
+- Issues #184/#185/#186 (types/statuses), #200/#214 (breaking data changes),
+ #205 (SIC codes), #217 (foreign_company_details), #179 (company address).
+
+## Delivered
+
+- Rebuilt the company-profile enum-ish fields onto the Roslyn-generated
+ string-backed value-type pattern: `CompanyStatusDetail` now comes from the
+ `company_status_detail` YAML group in the root `CompaniesHouse.Response`
+ namespace, `Jurisdiction` now comes from the `jurisdiction` YAML group in
+ `CompaniesHouse.Response.CompanyProfile`, and both hand-written wire enums
+ were removed. This brings company profile into the same unknown-value-safe
+ model already used by `CompanyStatus`/`CompanyType`.
+- Added two new generated company-profile value types from the live
+ `api-enumerations` data: `ForeignAccountType` and
+ `TermsOfAccountPublication`. `foreign_company_details.accounting_requirement`
+ now uses these value types directly, so future new wire values round-trip
+ without deserialization failures.
+- Extended `Response.CompanyProfile.CompanyProfile` to match the confirmed live
+ schema gaps: `subtype` (wired to generated `CompanySubtype`),
+ `has_super_secure_pscs`, `external_registration_number`, and the full
+ `foreign_company_details` object graph. The foreign-company model reuses the
+ existing `{day, month}` partial-date shape via `AccountingReferenceDate` for
+ `account_period_from` / `account_period_to`, and models
+ `must_file_within.months` as the raw string count returned by the API.
+- Extended `CompanyProfileLinks` with the missing `exemptions` and
+ `uk_establishments` links confirmed by real API payloads.
+- Added coverage across the stack: generated value-type round-trip tests for
+ `CompanyStatusDetail` and `Jurisdiction`; client-level company-profile tests
+ for realistic deserialization plus explicit 404 semantics; scenario
+ deserialization tests using captured plain/foreign/CIC payloads; and
+ integration assertions for the standard (`00445790`), foreign (`FC040879`)
+ and subtype (`13507518`) company profiles.
+- Verified: `dotnet build CompaniesHouse.slnx -c Release` with 0 errors;
+ `dotnet test tests\CompaniesHouse.Tests\CompaniesHouse.Tests.csproj -c Release`
+ passing; `dotnet test tests\CompaniesHouse.ScenarioTests\CompaniesHouse.ScenarioTests.csproj -c Release`
+ passing; `dotnet test tests\CompaniesHouse.SourceGenerator.Tests\CompaniesHouse.SourceGenerator.Tests.csproj -c Release`
+ passing 28/28 after the enum-map additions; whitespace formatting clean on
+ all touched files via `dotnet format whitespace --verify-no-changes`; and the
+ targeted company-profile integration tests passing 5/5 with a configured API
+ key.
diff --git a/.plans/completed/08-endpoint-officers.md b/.plans/completed/08-endpoint-officers.md
new file mode 100644
index 0000000..70f1571
--- /dev/null
+++ b/.plans/completed/08-endpoint-officers.md
@@ -0,0 +1,112 @@
+# 08 — Endpoint: Officers
+
+**Status:** complete
+**Depends on:** `01-core`, `03-value-types`; do after `07-company-profile`
+**Blocks:** nothing
+
+## Goal
+
+Rebuild the **Officers** endpoints from the current documentation.
+
+Docs:
+- List:
+- Get appointment:
+
+Paths (verify):
+- `GET /company/{companyNumber}/officers`
+- `GET /company/{companyNumber}/appointments/{appointmentId}`
+
+## Scope
+
+### Client
+- `ICompaniesHouseOfficersClient`:
+ - `GetOfficersAsync(companyNumber, startIndex = 0, pageSize = 35, ..., CancellationToken)`
+ - `GetOfficerAppointmentAsync(companyNumber, appointmentId, CancellationToken)`
+- Keep `OfficersUriBuilder` / `OfficersAppointmentUriBuilder` patterns.
+- The list endpoint supports `register_view`, `order_by`, `items_per_page`,
+ `start_index` — confirm and expose the useful ones.
+
+### Response models (faithful to docs)
+- List envelope: `total_results`, `items_per_page`, `start_index`,
+ `active_count`, `inactive_count`, `resigned_count`, `kind`, `links`, `items[]`.
+ - Ensure `total_results` is present and typed `int` (issues #206/#207).
+- Officer item / appointment:
+ - `officer_role` — **string-backed value type** (issues #197/#198:
+ `managing-officer` and other new roles kept breaking the old enum).
+ - `person_number` (issues #221/#222 — was missing).
+ - `address`, `date_of_birth` (month/year only — partial-date converter),
+ `appointed_on`, `resigned_on`, `nationality`, `occupation`,
+ `country_of_residence`, `identification` (+ `identification_type` value
+ type), `former_names`, `links.officer.appointments`,
+ `contact_details`, `principal_office_address`,
+ `responsibilities`/`is_pre_1992_appointment` where present.
+- Provide the computed `OfficerId` convenience the community relied on (issues
+ #169/#171 — it was deleted then restored). Derive from the appointments link.
+
+## Tasks
+
+- [ ] Confirm list + appointment schemas and query params from docs.
+- [ ] Build response models with value-type `officer_role`/`identification_type`
+ and `person_number`, `total_results:int`.
+- [ ] Restore the `OfficerId` computed property.
+- [ ] Wire sub-client + DI registrations.
+- [ ] Tests: URI builders, deserialization of sample payloads (incl. an unknown
+ officer role), integration tests.
+
+## Open questions
+
+- Default page size: API default is 35 for officers — match it rather than the
+ old 25. Confirm.
+
+## Acceptance criteria
+
+- Officer list and single appointment deserialize fully, including
+ `person_number` and `total_results`.
+- An unknown `officer_role` does not throw.
+- `OfficerId` is available on items.
+
+## References
+
+- Issues #197/#198 (officer roles), #206/#207 (total_results),
+ #221/#222 (person_number), #169/#171 (OfficerId), #165/#166 (get appointment).
+
+## Delivered
+
+- Rebuilt the officers wire enums onto the Roslyn-generated string-backed value
+ type pattern. `OfficerRole` now comes from the `officer_role` YAML group and
+ `OfficerIdentification.IdentificationType` now uses a generated
+ `IdentificationType` value type from the `identification_type` YAML group,
+ replacing the old hand-written enum/string model and preserving unknown future
+ wire values without deserialization failures.
+- Extended the officers response models to match the confirmed live schema:
+ list envelopes now include `etag`, `kind`, `links.self`, `inactive_count` and
+ `items_per_page`; officer/appointment items now include `etag`,
+ `person_number`, `is_pre_1992_appointment`, `identity_verification_details`,
+ `links.self`, and the live `appointed_before` field seen on historic
+ appointments. `total_results` remains a non-nullable `int`, matching repeated
+ real API responses.
+- Restored the `OfficerId` convenience on `Response.Officers.Officer` as a
+ computed, `[JsonIgnore]`d property derived from
+ `links.officer.appointments`, and hardened the shared parsing logic so missing
+ or malformed links return `null` rather than throwing.
+- Extended `GetOfficersAsync` and `OfficersUriBuilder` with the documented
+ optional query parameters `register_type`, `register_view` and `order_by`,
+ while keeping the established "only emit supplied optional parameters"
+ builder pattern. The officers endpoint default page size is now 35 instead of
+ 25 to match Companies House's documented behaviour.
+- Added coverage across the stack: URI-builder tests for the new query
+ parameters; client/unit tests using captured live list + appointment payloads;
+ new value-type round-trip tests for `OfficerRole` and `IdentificationType`;
+ scenario deserialization tests for the confirmed Tesco and Informa samples;
+ and live integration assertions for `00445790` and `03610056`, including the
+ identity-verification and corporate-identification shapes.
+- Verified: `dotnet build CompaniesHouse.slnx -c Release` with 0 errors;
+ `dotnet test tests\CompaniesHouse.Tests\CompaniesHouse.Tests.csproj -c Release`
+ passing; `dotnet test tests\CompaniesHouse.ScenarioTests\CompaniesHouse.ScenarioTests.csproj -c Release`
+ passing; `dotnet test tests\CompaniesHouse.SourceGenerator.Tests\CompaniesHouse.SourceGenerator.Tests.csproj -c Release`
+ passing 28/28 after the enum-map additions; `dotnet test tests\CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests\CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests.csproj -c Release`
+ passing; whitespace formatting clean on all touched files via
+ `dotnet format whitespace --verify-no-changes`; the new officers integration
+ tests passing 3/3 against the real API; and repeated live
+ `CompaniesHouseClient.GetOfficersAsync` / `GetOfficerByAppointmentIdAsync`
+ calls deserializing the confirmed payloads without throwing.
diff --git a/.plans/completed/09a-registered-office-address.md b/.plans/completed/09a-registered-office-address.md
new file mode 100644
index 0000000..ee51a60
--- /dev/null
+++ b/.plans/completed/09a-registered-office-address.md
@@ -0,0 +1,57 @@
+# 09a — Endpoint: Registered office address
+
+**Status:** complete
+**Depends on:** `01-core`, `07-company-profile`
+**Blocks:** nothing
+
+## Goal
+
+Rebuild the registered office address endpoint from the current docs and repeated live payloads.
+
+Docs:
+-
+
+Path:
+- `GET /company/{companyNumber}/registered-office-address`
+
+## Scope
+
+### Client
+- `ICompaniesHouseRegisteredOfficeAddressClient.GetRegisteredOfficeAddress(string companyNumber, CancellationToken)` hung off `CompaniesHouseClient` and DI.
+- Keep `RegisteredOfficeAddressUriBuilder`.
+
+### Response model
+- Faithful address shape with `kind`, `etag`, `links.self` and nullable address lines.
+- Preserve live `country` as a raw string, not an enum, because real payloads include values such as `South Africa`.
+
+## Tasks
+
+- [x] Confirm live payloads for UK and foreign companies.
+- [x] Remove enum assumptions from `country`.
+- [x] Expose the sub-client publicly and register it in DI.
+- [x] Add unit, scenario and integration coverage.
+
+## Open questions
+
+- None after live verification.
+
+## Acceptance criteria
+
+- UK and foreign registered-office payloads deserialize without enum failures.
+- The sub-client resolves from `CompaniesHouseClient` and DI.
+
+## References
+
+- Existing master implementation to replicate/modernise:
+ - `src/CompaniesHouse/CompaniesHouseRegisteredOfficeAddressClient.cs`
+ - `src/CompaniesHouse/ICompaniesHouseRegisteredOfficeAddressClient.cs`
+ - `src/CompaniesHouse/UriBuilders/RegisteredOfficeAddressUriBuilder.cs`
+ - `src/CompaniesHouse/Response/RegisteredOfficeAddress/OfficeAddress.cs`
+ - `src/CompaniesHouse/Response/RegisteredOfficeAddress/Links.cs`
+
+## Delivered
+
+- Verified live payloads for `00445790`, `FC040879` and `13507518` and rebuilt the model around the observed nullable contract.
+- Replaced the old `OfficeAddressCountry` wire enum with `string?` after confirming real API values are open-ended.
+- Made `ICompaniesHouseRegisteredOfficeAddressClient` public, added it to `ICompaniesHouseClient`, and registered it in the DI extension package.
+- Added client/unit coverage for captured live JSON, scenario deserialization coverage, DI resolution assertions, and integration assertions against the real API.
diff --git a/.plans/completed/09b-filing-history.md b/.plans/completed/09b-filing-history.md
new file mode 100644
index 0000000..d51a251
--- /dev/null
+++ b/.plans/completed/09b-filing-history.md
@@ -0,0 +1,64 @@
+# 09b — Endpoint: Filing history
+
+**Status:** complete
+**Depends on:** `01-core`, `03-value-types`
+**Blocks:** nothing
+
+## Goal
+
+Rebuild filing-history list and single-item endpoints against live Companies House payloads.
+
+Docs:
+-
+-
+
+Paths:
+- `GET /company/{companyNumber}/filing-history`
+- `GET /company/{companyNumber}/filing-history/{transactionId}`
+
+## Scope
+
+### Client
+- `ICompaniesHouseCompanyFilingHistoryClient` list + single-item methods.
+- Keep `CompanyFilingHistoryUriBuilder`.
+
+### Response model
+- Full filing-history envelope including paging fields and `links.self`.
+- Filing items with `action_date`, `links.document_metadata`, annotations, associated filings and resolutions.
+- Replace legacy wire enums with generated string-backed value types for filing category/status/subcategory/resolution category.
+- Support `subcategory` arriving as either a single string or an array.
+
+## Tasks
+
+- [x] Validate multiple live companies and single transactions.
+- [x] Migrate filing wire enums to generated value types.
+- [x] Harden single-or-array subcategory deserialization.
+- [x] Add unit, scenario, generator and integration coverage.
+
+## Open questions
+
+- None after live verification.
+
+## Acceptance criteria
+
+- Filing history list and single-item payloads deserialize from real API responses.
+- Unknown filing category/subcategory values round-trip without throwing.
+
+## References
+
+- Existing master implementation to replicate/modernise:
+ - `src/CompaniesHouse/CompaniesHouseCompanyFilingHistoryClient.cs`
+ - `src/CompaniesHouse/ICompaniesHouseCompanyFilingHistoryClient.cs`
+ - `src/CompaniesHouse/UriBuilders/CompanyFilingHistoryUriBuilder.cs`
+ - `src/CompaniesHouse/Response/CompanyFiling/CompanyFilingHistory.cs`
+ - `src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItem.cs`
+ - `src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemAssociatedFiling.cs`
+ - `src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemAnnotation.cs`
+ - `src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemResolution.cs`
+
+## Delivered
+
+- Verified live list and single-item payloads for `00445790`, `00002065` and `SC171417`, including mortgage filings and document links.
+- Migrated the old filing-history wire enums onto generated string-backed value types backed by new enum-map entries and generator overlay YAML.
+- Updated `EnumArrayOrSingleJsonConverterFactory` so generated value-type arrays can deserialize from either a single string or an array, matching live `subcategory` payloads.
+- Added client/unit tests, scenario deserialization coverage, value-type round-trip tests, generator inputs, and real-API integration assertions for list and single-item calls.
diff --git a/.plans/completed/09c-officer-appointments-list.md b/.plans/completed/09c-officer-appointments-list.md
new file mode 100644
index 0000000..f8e712b
--- /dev/null
+++ b/.plans/completed/09c-officer-appointments-list.md
@@ -0,0 +1,56 @@
+# 09c — Endpoint: Officer appointments list
+
+**Status:** complete
+**Depends on:** `01-core`, `03-value-types`, `08-officers`
+**Blocks:** nothing
+
+## Goal
+
+Modernise the officer appointments list endpoint and verify it against live natural-person and corporate-officer payloads.
+
+Docs:
+-
+
+Path:
+- `GET /officers/{officerId}/appointments`
+
+## Scope
+
+### Client
+- `ICompaniesHouseAppointmentsClient.GetAppointmentsAsync(...)` hung off `CompaniesHouseClient`.
+- Add a dedicated `AppointmentsUriBuilder` rather than inlined string concatenation.
+
+### Response model
+- Envelope counts, paging fields, `kind`, `name`, `links.self`, `is_corporate_officer` and `date_of_birth`.
+- Appointment items with `links.company`, `identification`, `is_pre_1992_appointment` and generated `CompanyStatus` / `OfficerRole` values.
+
+## Tasks
+
+- [x] Validate live natural-person and corporate-officer appointment lists.
+- [x] Move URI construction into a dedicated builder.
+- [x] Expand the response envelope and item models to match live payloads.
+- [x] Add unit, scenario and integration coverage.
+
+## Open questions
+
+- None after live verification.
+
+## Acceptance criteria
+
+- Natural and corporate appointment lists deserialize fully from the live API.
+- URI construction follows the standard builder pattern.
+
+## References
+
+- Existing master implementation to replicate/modernise:
+ - `src/CompaniesHouse/CompaniesHouseAppointmentsClient.cs`
+ - `src/CompaniesHouse/ICompaniesHouseAppointmentsClient.cs`
+ - `src/CompaniesHouse/Response/Appointments/Appointments.cs`
+ - `src/CompaniesHouse/Response/Appointments/Appointment.cs`
+
+## Delivered
+
+- Verified real officer appointment payloads for `uQNQ-blSo-8PiOaehWClTPmbZNI` and `YwIOmduyS6PW5axJgQQrsTGyRD0`.
+- Introduced `IAppointmentsUriBuilder` / `AppointmentsUriBuilder` and updated the client to use the shared URI-builder pattern.
+- Expanded the appointments envelope and item models with the observed counts, links, identification and corporate-officer fields.
+- Added dedicated client/unit tests, URI-builder tests, scenario deserialization coverage and integration assertions for both officer shapes.
diff --git a/.plans/completed/09d-persons-with-significant-control-list.md b/.plans/completed/09d-persons-with-significant-control-list.md
new file mode 100644
index 0000000..a6b458f
--- /dev/null
+++ b/.plans/completed/09d-persons-with-significant-control-list.md
@@ -0,0 +1,59 @@
+# 09d — Endpoint: Persons with significant control list
+
+**Status:** complete
+**Depends on:** `01-core`, `03-value-types`, `07-company-profile`
+**Blocks:** `09j-psc-detail-types`
+
+## Goal
+
+Rebuild the existing PSC list endpoint against live payloads and align it with generated value types.
+
+Docs:
+-
+
+Path:
+- `GET /company/{companyNumber}/persons-with-significant-control`
+
+## Scope
+
+### Client
+- `ICompaniesHousePersonsWithSignificantControlClient.GetPersonsWithSignificantControlAsync(...)`.
+- Keep `PersonsWithSignificantControlBuilder`.
+
+### Response model
+- Full envelope with paging fields, `links.self`, `active_count`, `ceased_count` and `total_results`.
+- Items for individuals and corporate entities, including `ceased`, `ceased_on`, identification, links and generated PSC kind/nature-of-control value types.
+
+## Tasks
+
+- [x] Validate companies with different live PSC shapes.
+- [x] Migrate PSC kind and nature-of-control wire enums to generated value types.
+- [x] Fill the envelope/item schema gaps found in live responses.
+- [x] Add unit, scenario, generator and integration coverage.
+
+## Open questions
+
+- PSC statements and detail endpoints remain for `09j`.
+
+## Acceptance criteria
+
+- Live PSC list payloads deserialize for both corporate and individual records.
+- Unknown PSC kind/nature values do not throw.
+
+## References
+
+- Existing master implementation to replicate/modernise:
+ - `src/CompaniesHouse/CompaniesHousePersonsWithSignificantControlClient.cs`
+ - `src/CompaniesHouse/ICompaniesHousePersonsWithSignificantControlClient.cs`
+ - `src/CompaniesHouse/UriBuilders/PersonsWithSignificantControlBuilder.cs`
+ - `src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonsWithSignificantControl.cs`
+ - `src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControl.cs`
+ - `src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlIdentification.cs`
+ - `src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlLinks.cs`
+
+## Delivered
+
+- Verified repeated live PSC list payloads across companies including `03977902`, `03610056`, `09965459`, `06768813` and `07560766`.
+- Migrated PSC kind and nature-of-control from hand-written wire enums to generated string-backed value types backed by new enum-map and overlay YAML entries.
+- Expanded the list envelope and PSC item models with the missing paging, link, identification and ceased-state fields seen in the real API.
+- Added client/unit tests, scenario deserialization coverage, value-type tests and real-API integration assertions for the list endpoint.
diff --git a/.plans/completed/09e-charges.md b/.plans/completed/09e-charges.md
new file mode 100644
index 0000000..7b85cd2
--- /dev/null
+++ b/.plans/completed/09e-charges.md
@@ -0,0 +1,60 @@
+# 09e — Endpoint: Charges
+
+**Status:** complete
+**Depends on:** `01-core`, `03-value-types`
+**Blocks:** nothing
+
+## Goal
+
+Rebuild company charges list and single-charge endpoints from live payloads and modernise the old wire-enum model.
+
+Docs:
+-
+-
+
+Paths:
+- `GET /company/{companyNumber}/charges`
+- `GET /company/{companyNumber}/charges/{chargeId}`
+
+## Scope
+
+### Client
+- `ICompaniesHouseChargesClient` list + single methods.
+- Keep `ChargesUriBuilder`.
+
+### Response model
+- Envelope fields including `etag`, `unfiltered_count`, `satisfied_count` and `part_satisfied_count`.
+- Charge item/detail fields including classification, particulars, secured details, transactions, insolvency cases and links.
+- Generated value types for charge status, classification type, particulars type, secured-details type and assets-ceased/released.
+
+## Tasks
+
+- [x] Validate multiple live charge lists and single charges.
+- [x] Fix JSON property-name mismatches and nullable gaps.
+- [x] Migrate legacy charge wire enums to generated value types.
+- [x] Add unit, scenario, generator and integration coverage.
+
+## Open questions
+
+- None after live verification.
+
+## Acceptance criteria
+
+- Charges list and single-charge payloads deserialize from the live API.
+- Unknown charge enum-ish values do not throw.
+
+## References
+
+- Existing master implementation to replicate/modernise:
+ - `src/CompaniesHouse/CompaniesHouseChargesClient.cs`
+ - `src/CompaniesHouse/ICompaniesHouseChargesClient.cs`
+ - `src/CompaniesHouse/UriBuilders/ChargesUriBuilder.cs`
+ - `src/CompaniesHouse/Response/Charges/Charges.cs`
+ - `src/CompaniesHouse/Response/Charges/Charge.cs`
+
+## Delivered
+
+- Verified real charge payloads for `03977902` and `00002065`, including list and single-charge calls.
+- Corrected the legacy schema mismatches (`etag`, `unfiltered_count`, nullable nested shapes) and aligned the charge models with the observed API contract.
+- Migrated the old charge wire enums to generated string-backed value types using new enum-map entries and overlay YAML inputs.
+- Added client/unit tests, URI-builder tests, scenario deserialization coverage, value-type tests and integration assertions for both charge endpoints.
diff --git a/.plans/completed/09f-insolvency.md b/.plans/completed/09f-insolvency.md
new file mode 100644
index 0000000..cfd4f66
--- /dev/null
+++ b/.plans/completed/09f-insolvency.md
@@ -0,0 +1,61 @@
+# 09f — Endpoint: Insolvency
+
+**Status:** complete
+**Depends on:** `01-core`, `03-value-types`
+**Blocks:** nothing
+
+## Goal
+
+Modernise the insolvency endpoint against real API responses and move its wire enums onto generated value types.
+
+Docs:
+-
+
+Path:
+- `GET /company/{companyNumber}/insolvency`
+
+## Scope
+
+### Client
+- `ICompaniesHouseCompanyInsolvencyInformationClient.GetCompanyInsolvencyInformationAsync(...)`.
+- Add a dedicated `CompanyInsolvencyInformationUriBuilder`.
+
+### Response model
+- `status[]`, `cases[]`, case dates, practitioners, addresses and links.
+- Generated value types for insolvency status, case-date type and case type.
+- Nullable handling that matches observed live payloads.
+
+## Tasks
+
+- [x] Validate multiple live insolvency payloads.
+- [x] Move URI construction into a builder.
+- [x] Migrate insolvency wire enums to generated value types.
+- [x] Add unit, scenario, generator and integration coverage.
+
+## Open questions
+
+- None after live verification.
+
+## Acceptance criteria
+
+- Insolvency payloads deserialize from real Companies House responses.
+- Unknown case/status/date-type values do not throw.
+
+## References
+
+- Existing master implementation to replicate/modernise:
+ - `src/CompaniesHouse/CompaniesHouseCompanyInsolvencyInformationClient.cs`
+ - `src/CompaniesHouse/ICompaniesHouseCompanyInsolvencyInformationClient.cs`
+ - `src/CompaniesHouse/Response/Insolvency/CompanyInsolvencyInformation.cs`
+ - `src/CompaniesHouse/Response/Insolvency/Case.cs`
+ - `src/CompaniesHouse/Response/Insolvency/CaseDate.cs`
+ - `src/CompaniesHouse/Response/Insolvency/Practitioner.cs`
+ - `src/CompaniesHouse/Response/Insolvency/Address.cs`
+ - `src/CompaniesHouse/Response/Insolvency/Links.cs`
+
+## Delivered
+
+- Verified live insolvency payloads for `08749409` and `07560766` and rebuilt the models around the observed nullable shapes.
+- Introduced `ICompanyInsolvencyInformationUriBuilder` / `CompanyInsolvencyInformationUriBuilder` and updated the client to use the shared URI-builder pattern.
+- Migrated insolvency status, case-date type and case type from hand-written wire enums/strings to generated string-backed value types.
+- Added client/unit tests, URI-builder tests, scenario deserialization coverage, value-type tests and real-API integration assertions.
diff --git a/.plans/completed/09g-documents.md b/.plans/completed/09g-documents.md
new file mode 100644
index 0000000..123447c
--- /dev/null
+++ b/.plans/completed/09g-documents.md
@@ -0,0 +1,64 @@
+# 09g — Endpoint: Documents
+
+**Status:** complete
+**Depends on:** `01-core`
+**Blocks:** nothing
+
+## Goal
+
+Verify the Document API metadata and download endpoints, including the separate host and content-download behavior.
+
+Docs:
+-
+-
+
+Paths:
+- `GET /document/{documentId}` (metadata, via the document API host)
+- `GET /document/{documentId}/content`
+
+## Scope
+
+### Client
+- `ICompaniesHouseDocumentMetadataClient` and `ICompaniesHouseDocumentDownloadClient` exposed through `ICompaniesHouseDocumentClient` / `CompaniesHouseClient`.
+- Keep dedicated metadata/content URI builders.
+
+### Response model
+- Metadata with `filename`, `created_at`, nullable `significant_date`, `links`, and resource content lengths large enough for real files.
+- Download handling that preserves content headers and stream length.
+
+## Tasks
+
+- [x] Validate real metadata responses and at least one live content download.
+- [x] Fix metadata field types and nullability based on the real API.
+- [x] Add unit, scenario and integration coverage.
+- [x] Verify URI/host construction for the separate document API host.
+
+## Open questions
+
+- None after live verification.
+
+## Acceptance criteria
+
+- Document metadata and download calls succeed against the live API.
+- Metadata fields match the observed real payload types.
+
+## References
+
+- Existing master implementation to replicate/modernise:
+ - `src/CompaniesHouse/CompaniesHouseDocumentClient.cs`
+ - `src/CompaniesHouse/CompaniesHouseDocumentMetadataClient.cs`
+ - `src/CompaniesHouse/CompaniesHouseDocumentDownloadClient.cs`
+ - `src/CompaniesHouse/ICompaniesHouseDocumentClient.cs`
+ - `src/CompaniesHouse/ICompaniesHouseDocumentMetadataClient.cs`
+ - `src/CompaniesHouse/ICompaniesHouseDocumentDownloadClient.cs`
+ - `src/CompaniesHouse/UriBuilders/DocumentMetadataUriBuilder.cs`
+ - `src/CompaniesHouse/UriBuilders/DocumentContentUriBuilder.cs`
+ - `src/CompaniesHouse/Response/Document/DocumentMetadata.cs`
+ - `src/CompaniesHouse/Response/Document/DocumentDownload.cs`
+
+## Delivered
+
+- Verified live metadata responses for filing-history documents and confirmed live content download behavior against the document API host.
+- Updated document metadata types to match real payloads: `CreatedAt` and `SignificantDate` are typed dates, `Filename` is modelled explicitly, and resource content lengths now support large values.
+- Added URI-builder tests, client/unit tests, scenario deserialization coverage and live integration assertions for both metadata and content download.
+- Confirmed that document download succeeds without forcing an `Accept: application/json` header, which the live endpoint rejects with `406`.
diff --git a/.plans/completed/09h-registers.md b/.plans/completed/09h-registers.md
new file mode 100644
index 0000000..7905770
--- /dev/null
+++ b/.plans/completed/09h-registers.md
@@ -0,0 +1,40 @@
+# 09h — Endpoint: Registers
+
+**Status:** completed
+**Depends on:** `01-core`
+**Blocks:** nothing
+
+## Goal
+
+Build the company registers endpoint from the live API docs.
+
+Docs:
+-
+
+Path:
+- `GET /company/{companyNumber}/registers`
+
+## Scope
+
+- Add a focused registers sub-client, URI builder and response models.
+- Verify the live response shape before modelling fields.
+
+## Tasks
+
+- [x] Confirm the live docs and payload shape.
+- [x] Build client, URI builder and response model.
+- [x] Add unit, scenario and integration coverage.
+
+## Open questions
+
+- Which registers fields are actually present in live payloads, and are they link-only or richer nested objects?
+ - Answered: live payloads can be sparse and omit several fields marked required in the spec (for example, `company_number`, some register sections, and `links` inside register items).
+
+## Acceptance criteria
+
+- A real registers response deserializes cleanly from the live API.
+
+## References
+
+- No existing master implementation - build from the live API docs only.
+-
diff --git a/.plans/completed/09i-disqualified-officers-detail.md b/.plans/completed/09i-disqualified-officers-detail.md
new file mode 100644
index 0000000..4138412
--- /dev/null
+++ b/.plans/completed/09i-disqualified-officers-detail.md
@@ -0,0 +1,43 @@
+# 09i — Endpoint: Disqualified officers detail
+
+**Status:** completed
+**Depends on:** `01-core`, `06-search`
+**Blocks:** nothing
+
+## Goal
+
+Build the natural-person and corporate disqualified-officer detail endpoints from the live API docs.
+
+Docs:
+-
+-
+
+Paths:
+- `GET /disqualified-officers/natural/{officerId}`
+- `GET /disqualified-officers/corporate/{officerId}`
+
+## Scope
+
+- Add a focused sub-client, URI builders and response models for natural and corporate detail payloads.
+- Reuse generated value types where the live schema exposes enum-ish fields.
+
+## Tasks
+
+- [x] Confirm live docs and payload examples.
+- [x] Build the client, builders and models.
+- [x] Add unit, scenario and integration coverage.
+
+## Open questions
+
+- Are the natural and corporate payloads structurally distinct enough to justify separate response types?
+ - Answered: yes; they share common nested shapes but differ in top-level identity fields (`surname`/name parts vs `name`, optional registration metadata), so separate top-level response types are clearer.
+
+## Acceptance criteria
+
+- Both disqualified-officer detail endpoints deserialize from the live API.
+
+## References
+
+- No existing master implementation - build from the live API docs only.
+-
+-
diff --git a/.plans/completed/09j-psc-detail-types.md b/.plans/completed/09j-psc-detail-types.md
new file mode 100644
index 0000000..3341650
--- /dev/null
+++ b/.plans/completed/09j-psc-detail-types.md
@@ -0,0 +1,47 @@
+# 09j — Endpoint: PSC detail types
+
+**Status:** completed
+**Depends on:** `09d-persons-with-significant-control-list`, `01-core`, `03-value-types`
+**Blocks:** nothing
+
+## Goal
+
+Build the remaining PSC detail endpoints: individual, corporate entity, legal person, statements and super-secure PSCs.
+
+Docs:
+-
+-
+-
+-
+-
+-
+
+## Scope
+
+- Add the missing PSC detail/statement clients, builders and response models.
+- Reuse the generated PSC kind/nature-of-control value types introduced in `09d`.
+
+## Tasks
+
+- [x] Confirm live docs and identify stable test IDs.
+- [x] Build the client surface and response models for each PSC detail family.
+- [x] Add unit, scenario and integration coverage.
+
+## Open questions
+
+- Which live companies expose stable statement and super-secure test data?
+ - Resolved pragmatically: stable individual/corporate/statement IDs are covered in integration tests; legal/super-secure probes run against sampled live companies and validate when present.
+
+## Acceptance criteria
+
+- PSC detail, statement and super-secure endpoints deserialize from the live API.
+
+## References
+
+- No existing master implementation - build from the live API docs only.
+-
+-
+-
+-
+-
+-
diff --git a/.plans/completed/09k-exemptions.md b/.plans/completed/09k-exemptions.md
new file mode 100644
index 0000000..8122155
--- /dev/null
+++ b/.plans/completed/09k-exemptions.md
@@ -0,0 +1,40 @@
+# 09k — Endpoint: Exemptions
+
+**Status:** completed
+**Depends on:** `01-core`, `03-value-types`
+**Blocks:** nothing
+
+## Goal
+
+Build the company exemptions endpoint from the live API docs.
+
+Docs:
+-
+
+Path:
+- `GET /company/{companyNumber}/exemptions`
+
+## Scope
+
+- Add a focused exemptions sub-client, URI builder and response model.
+- Decide whether any exemption-description values should come from generated string-backed value types.
+
+## Tasks
+
+- [x] Confirm the live docs and payload schema.
+- [x] Build client, builder and response model.
+- [x] Add unit, scenario and integration coverage.
+
+## Open questions
+
+- Which exemption-description group in `api-enumerations` should back any enum-ish fields?
+ - Answered for now: no generated exemption-specific value type exists in this repo yet, so exemption type fields are modelled as raw strings and preserve wire values.
+
+## Acceptance criteria
+
+- A real exemptions response deserializes cleanly from the live API.
+
+## References
+
+- No existing master implementation - build from the live API docs only.
+-
diff --git a/.plans/completed/09l-uk-establishments.md b/.plans/completed/09l-uk-establishments.md
new file mode 100644
index 0000000..8120b36
--- /dev/null
+++ b/.plans/completed/09l-uk-establishments.md
@@ -0,0 +1,40 @@
+# 09l — Endpoint: UK establishments
+
+**Status:** completed
+**Depends on:** `01-core`, `07-company-profile`
+**Blocks:** nothing
+
+## Goal
+
+Build the UK establishments endpoint from the live API docs.
+
+Docs:
+-
+
+Path:
+- `GET /company/{companyNumber}/uk-establishments`
+
+## Scope
+
+- Add a focused UK-establishments sub-client, URI builder and response model.
+- Verify the live payload shape, especially address and linkage fields.
+
+## Tasks
+
+- [x] Confirm the docs and live schema.
+- [x] Build client, builder and model.
+- [x] Add unit, scenario and integration coverage.
+
+## Open questions
+
+- Are UK establishments returned as a simple list or a paged envelope?
+ - Answered: live responses are a simple list envelope (`etag`, `kind`, `links`, `items`) without paging fields.
+
+## Acceptance criteria
+
+- A real UK establishments response deserializes from the live API.
+
+## References
+
+- No existing master implementation - build from the live API docs only.
+-
diff --git a/.plans/completed/10-testing-strategy.md b/.plans/completed/10-testing-strategy.md
new file mode 100644
index 0000000..472aa4c
--- /dev/null
+++ b/.plans/completed/10-testing-strategy.md
@@ -0,0 +1,131 @@
+# 10 — Testing strategy
+
+**Status:** complete
+**Depends on:** `00-foundation`; runs continuously alongside every plan
+**Blocks:** nothing (but gates "done" for each plan)
+
+## Goal
+
+Define and stand up the test approach for v-next so every endpoint and the enum
+generator ship with meaningful coverage, and so the historical "it broke in
+production on a new value" class of bug is caught by tests.
+
+## Test projects (existing, to modernise)
+
+- `tests/CompaniesHouse.Tests` — fast **unit tests** (URI builders, converters,
+ value types, response mapping).
+- `tests/CompaniesHouse.ScenarioTests` — **behavioural** tests against canned
+ HTTP responses (no network).
+- `tests/CompaniesHouse.IntegrationTests` — hit the **real API** (needs the
+ `api_key` env var); skipped/soft-failed without it.
+- `tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests` — DI
+ registration/resolution tests.
+
+Retarget all to the new TFMs (plan `00`), remove `Newtonsoft.Json` from tests
+(issues #177/#178/#195/#196 were all Newtonsoft bumps — delete the dependency),
+enable nullable.
+
+## What to test
+
+### Value types & generator (highest priority — this is the whole point)
+- **Unknown value never throws**: deserialize a status/type/role string that is
+ *not* in the YAML; assert it round-trips and `IsKnown == false`.
+- Known values equal their static members; equality/`GetHashCode`; `ToString`.
+- Null/empty/absent semantics.
+- Prefix helpers (e.g. filing subcategory) where used.
+- **Generator snapshot tests**: given a small YAML input, assert the generated
+ source matches a checked-in snapshot (use `Microsoft.CodeAnalysis` test host
+ or a verify library). Guards against accidental generator regressions.
+
+### Per-endpoint
+- **URI builder** unit tests: correct path + query for each param combination,
+ proper escaping, and *omission* of optional params (e.g. the `restrictions`
+ bug, #203/#204/#208).
+- **Deserialization scenario tests**: feed **real sample payloads** (captured
+ from the API / docs) through the client via a stubbed `HttpMessageHandler`
+ (or WireMock) and assert the mapped model — including the fields that were
+ historically missing (`person_number`, `total_results`, `foreign_company_details`,
+ `sic_codes`).
+- **Numbers-as-strings** (#212), **partial dates**, **404 → null data +
+ status** (plan `01`).
+- **Error/transport**: 429 surfaces `RetryAfter` (#181/#182); non-2xx surfaces
+ status + headers.
+
+### DI
+- Every sub-client interface resolves from the container.
+- Options bind from `IConfiguration`; missing API key fails `ValidateOnStart`.
+
+## Tooling
+
+- **Test runner: xUnit.** **Assertions: Shouldly.** (NUnit and FluentAssertions
+ have been fully removed as of plan `00` — FluentAssertions' license changed
+ to a paid tier from v8 onward.) For deep object-graph comparisons that need
+ to bridge a raw wire string against a string-backed value type, use the
+ repo's own `EquivalencyAssertionExtensions.ShouldBeEquivalentTo(...)` helper
+ in `CompaniesHouse.Tests` rather than reaching for a new dependency.
+- Add an HTTP stubbing approach for scenario tests (a hand-rolled
+ `HttpMessageHandler` stub, or `WireMock.Net`). Decide and standardise.
+- Snapshot/verify library for generator output (e.g. `Verify`).
+- CI already publishes TRX results — keep that.
+
+## Tasks
+
+- [ ] Retarget test projects; strip Newtonsoft; enable nullable.
+- [ ] Establish the HTTP-stub helper for scenario tests.
+- [ ] Add value-type + generator snapshot tests.
+- [ ] Add a per-endpoint test checklist (mirror plan `09`'s per-endpoint list).
+- [ ] Wire integration tests to skip cleanly without `api_key`.
+
+## Acceptance criteria
+
+- `dotnet test -c Release` is green offline (integration tests skip without a
+ key).
+- Every shipped endpoint has URI-builder + deserialization coverage.
+- The generator has snapshot coverage.
+
+## References
+
+- Issues #177/#178/#195/#196 (remove Newtonsoft from tests), #180 (sandbox),
+ #181/#182 (error metadata), plus all the endpoint-specific issues in `09`.
+
+## Delivered
+
+Most of this plan's tasks were already satisfied incrementally by plans
+`00`/`04`/`06`/`07`/`08`/`09a`-`09g` (xUnit + Shouldly test stack, nullable
+enabled, `net10.0`-targeted test projects, no `Newtonsoft.Json` anywhere in
+`tests/`, value-type unit tests with unknown-value coverage, real-payload
+scenario tests per endpoint). This pass closed the remaining gaps:
+
+- **Integration tests now skip cleanly without `COMPANIES_HOUSE_API_KEY`**
+ (previously they'd fail with auth/deserialization errors). Added
+ `IntegrationFactAttribute`/`IntegrationTheoryAttribute`
+ (`tests/CompaniesHouse.IntegrationTests/IntegrationFactAttribute.cs`,
+ `IntegrationTheoryAttribute.cs`) which set `Skip` at attribute-construction
+ time when `Keys.ApiKeyOrNull` is null/empty, and applied them to every
+ `[Fact]`/`[Theory]` across the whole `CompaniesHouse.IntegrationTests`
+ project. Verified: 73/73 pass with the key present, all tests skip cleanly
+ (0 failed) with the env var unset.
+- **Generator snapshot test** added
+ (`tests/CompaniesHouse.SourceGenerator.Tests/ValueTypeEmitterSnapshotTests.cs`)
+ - asserts the *entire* generated value-type + JSON-converter source text
+ for a representative enum group against a fixed expected string, on top
+ of the existing spot-check (`ShouldContain`) assertions in
+ `EnumValueTypeGeneratorTests`. No new snapshot-testing package dependency
+ was introduced (a plain Shouldly `ShouldBe` on the full source string is
+ sufficient and keeps the dependency footprint the same).
+- Confirmed already-satisfied items via direct inspection rather than
+ re-doing them: `Directory.Build.props` enables `Nullable`/`ImplicitUsings`
+ repo-wide; no `Newtonsoft` references anywhere under `tests/`; 429/`RetryAfter`
+ and non-2xx/server-error transport behaviour already has dedicated coverage
+ in `HttpResponseMessageExtensionsTests`; every endpoint shipped so far
+ (search, company profile, officers, and the plan `09a`-`09g` catalogue)
+ already carries URI-builder + scenario + integration coverage as part of
+ its own plan.
+
+Verification: `dotnet build CompaniesHouse.slnx -c Release` (0 errors),
+`CompaniesHouse.Tests` 458/458, `ScenarioTests` 20/20, `SourceGenerator.Tests`
+29/29, DI tests 6/6, `IntegrationTests` 73/73 with a key / all skip cleanly
+without one, `dotnet format --verify-no-changes` scoped to touched files
+(pre-existing unrelated whitespace issues on untouched lines in a handful of
+older integration test files were left as-is, consistent with not fixing
+unrelated pre-existing issues).
diff --git a/.plans/outstanding/09-endpoint-catalogue-remaining.md b/.plans/outstanding/09-endpoint-catalogue-remaining.md
new file mode 100644
index 0000000..f1fcaaa
--- /dev/null
+++ b/.plans/outstanding/09-endpoint-catalogue-remaining.md
@@ -0,0 +1,23 @@
+# 09 — Remaining endpoint catalogue (split index)
+
+**Status:** split
+**Depends on:** `01-core`, `03-value-types`
+**Blocks:** nothing
+
+## Split plans
+
+Completed in this change:
+- `..\completed\09a-registered-office-address.md`
+- `..\completed\09b-filing-history.md`
+- `..\completed\09c-officer-appointments-list.md`
+- `..\completed\09d-persons-with-significant-control-list.md`
+- `..\completed\09e-charges.md`
+- `..\completed\09f-insolvency.md`
+- `..\completed\09g-documents.md`
+- `..\completed\09h-registers.md`
+- `..\completed\09i-disqualified-officers-detail.md`
+- `..\completed\09j-psc-detail-types.md`
+- `..\completed\09k-exemptions.md`
+- `..\completed\09l-uk-establishments.md`
+
+Still outstanding:
diff --git a/.plans/outstanding/11-docs-samples-migration.md b/.plans/outstanding/11-docs-samples-migration.md
new file mode 100644
index 0000000..5427551
--- /dev/null
+++ b/.plans/outstanding/11-docs-samples-migration.md
@@ -0,0 +1,74 @@
+# 11 — Docs, samples & migration guide
+
+**Status:** in progress - README, sample project and MIGRATION.md rewritten
+for the endpoints landed so far (search, company profile, officers,
+appointments, filing history, insolvency, PSC, charges, registered office
+address, documents). Still needs a pass once the remaining endpoints in
+`09h`-`09l` land (registers, disqualified officer detail, PSC detail types,
+exemptions, UK establishments), and a final cross-check near release.
+**Depends on:** endpoints as they land; finalise near release
+**Blocks:** the v-next release announcement
+
+## Goal
+
+Refresh all consumer-facing documentation for v-next: the README, the runnable
+sample, and a clear **migration guide** from the previous major, since v-next is
+deliberately breaking.
+
+## Scope
+
+### README
+- Rewrite for the new setup:
+ - Installation (both packages), supported TFMs (net8/9/10).
+ - `CompaniesHouseClient` usage (settings + `HttpClient` construction paths).
+ - **DI section** using the new `IOptions<>` overloads and `IConfiguration`
+ binding (plan `02`).
+ - **Enum/value-type section**: explain string-backed value types, why unknown
+ values never throw, `IsKnown`, `Description`, and how to pattern-match
+ (link the blog post).
+ - Response model: how to read `StatusCode`/`RetryAfter`/`Data` (plan `01`).
+ - Per-endpoint usage snippets as endpoints land.
+- Fix stale bits: the AppVeyor badge (CI is GitHub Actions now), the 2020
+ copyright, and the old base URL / API-key portal links.
+
+### Sample project
+- Update `samples/SampleProject` to the new client + DI, demonstrating search,
+ company profile, officers, and handling an unknown enum value gracefully.
+
+### Migration guide (`MIGRATION.md` or a README section)
+- Enumerate the breaking changes:
+ - Target frameworks dropped (`netstandard`/`net45`).
+ - `Newtonsoft.Json` → `System.Text.Json` (custom converters replaced).
+ - **All enums → string-backed value types** (biggest behavioural change; show
+ before/after for a `switch`).
+ - `CompaniesHouseClientResponse` shape change (now carries status/headers).
+ - Default base URI change (`companieshouse.gov.uk` →
+ `company-information.service.gov.uk`).
+ - DI API changes (options/overloads); any renamed methods/interfaces.
+- Provide copy-paste before/after snippets for the common cases.
+
+### Contributor docs
+- Ensure `AGENTS.md` and `.plans/README.md` stay accurate.
+- Document the "refresh enumerations" and "release" flows.
+
+## Tasks
+
+- [x] Rewrite README for v-next (progressive, per-endpoint) - done for all
+ endpoints landed so far; add a snippet for each of `09h`-`09l` as they land.
+- [x] Update the sample project (direct construction, DI, search, company
+ profile, officers, graceful unknown-enum handling).
+- [x] Write the migration guide with before/after snippets (`MIGRATION.md`).
+- [x] Fix stale badges/links (AppVeyor → GitHub Actions badge, developer
+ portal link, base URI). No stale copyright text was present in the README.
+- [ ] Cross-check AGENTS.md + plans are current at release.
+
+## Acceptance criteria
+
+- A new user can install, configure (via DI and directly), and make a search +
+ profile call by following the README alone.
+- The migration guide covers every breaking change with a before/after example.
+
+## References
+
+- Blog (enum rationale):
+- API reference:
diff --git a/.plans/outstanding/12-response-discriminated-union.md b/.plans/outstanding/12-response-discriminated-union.md
new file mode 100644
index 0000000..8508e32
--- /dev/null
+++ b/.plans/outstanding/12-response-discriminated-union.md
@@ -0,0 +1,295 @@
+# 12 — Discriminated union response type (issue #189)
+
+**Status:** outstanding
+**Depends on:** `01-core-client-architecture` (complete)
+**Blocks:** nothing — but improves ergonomics for all endpoint consumers
+
+## Goal
+
+Replace the flat `CompaniesHouseClientResponse` (with a nullable `Data` and
+an `IsSuccess` flag callers must remember to check) with a proper **discriminated
+union** — an abstract base type `CompaniesHouseResponse` whose sealed nested
+subtypes represent every distinct HTTP outcome the API produces. Consumers
+pattern-match on the concrete type; the compiler guides them rather than silent
+null-reference bugs.
+
+This directly implements the original intent of issue #189 ("a base class …
+switching based on the concrete class") and supersedes the interim shape shipped
+in plan `01`.
+
+## Why
+
+The current flat class has two problems:
+
+1. **`Data` is always nullable.** Even on success, callers must write
+ `if (response.IsSuccess && response.Data is not null)`. There is nothing
+ stopping them from reading `response.Data` on a 404 and getting `null`
+ silently.
+2. **Semantics are collapsed.** A 404, a 429, and a 401 are three very different
+ situations with different recovery paths. Today they all look the same to the
+ compiler: `IsSuccess == false`. Callers must remember to inspect `StatusCode`
+ themselves.
+
+A sealed type hierarchy solves both: `Success.Data` is always non-null (no `?`),
+and the switch/is-pattern forces the caller to reason about each outcome.
+
+## Proposed shape
+
+```csharp
+///
+/// Discriminated union representing every HTTP outcome of a Companies House API
+/// call. Transport failures (network errors, DNS, timeout) surface as
+/// from the underlying HttpClient.
+///
+public abstract class CompaniesHouseResponse
+{
+ // Private constructor — no external subclassing.
+ private CompaniesHouseResponse(int statusCode, string? reasonPhrase)
+ {
+ StatusCode = statusCode;
+ ReasonPhrase = reasonPhrase;
+ }
+
+ /// The HTTP status code of the response.
+ public int StatusCode { get; }
+
+ /// The HTTP reason phrase, if any.
+ public string? ReasonPhrase { get; }
+
+ ///
+ /// Returns the deserialized response body when this is a
+ /// response. Throws for any other subtype,
+ /// making the error explicit rather than silently returning null.
+ /// Use pattern matching when you need to handle non-success outcomes.
+ ///
+ ///
+ /// Thrown when the response is not .
+ ///
+ public T Data => this is Success s
+ ? s.Data
+ : throw new InvalidOperationException(
+ $"Cannot access Data on a {GetType().Name} response (HTTP {StatusCode}).");
+
+ // ─── Subtypes ────────────────────────────────────────────────────────────
+
+ /// A 2xx response whose body deserialized successfully.
+ public sealed class Success : CompaniesHouseResponse
+ {
+ public Success(T data, int statusCode, string? reasonPhrase, HttpResponseHeaders headers)
+ : base(statusCode, reasonPhrase)
+ {
+ Data = data;
+ Headers = headers;
+ }
+
+ /// The deserialized response body. Never null on this subtype.
+ public T Data { get; }
+
+ /// The full set of response headers.
+ public HttpResponseHeaders Headers { get; }
+ }
+
+ ///
+ /// A 404 response — the requested resource does not exist or is not
+ /// accessible with the provided credentials.
+ ///
+ public sealed class NotFound : CompaniesHouseResponse
+ {
+ public NotFound(int statusCode, string? reasonPhrase) : base(statusCode, reasonPhrase) {}
+ }
+
+ ///
+ /// A 429 response — the client has been rate-limited. Check
+ /// before retrying.
+ ///
+ public sealed class RateLimited : CompaniesHouseResponse
+ {
+ public RateLimited(TimeSpan? retryAfter, int statusCode, string? reasonPhrase)
+ : base(statusCode, reasonPhrase) => RetryAfter = retryAfter;
+
+ /// How long to wait before retrying, if the server supplied the header.
+ public TimeSpan? RetryAfter { get; }
+ }
+
+ ///
+ /// A 401/403 response — the API key is missing, wrong, or lacks permission.
+ ///
+ public sealed class Unauthorized : CompaniesHouseResponse
+ {
+ public Unauthorized(int statusCode, string? reasonPhrase) : base(statusCode, reasonPhrase) {}
+ }
+
+ ///
+ /// Any other 4xx response not covered by the more specific subtypes.
+ ///
+ public sealed class ClientError : CompaniesHouseResponse
+ {
+ public ClientError(int statusCode, string? reasonPhrase) : base(statusCode, reasonPhrase) {}
+ }
+
+ ///
+ /// A 5xx response — the server encountered an error. May carry a
+ /// hint (e.g. 503 with Retry-After).
+ ///
+ public sealed class ServerError : CompaniesHouseResponse
+ {
+ public ServerError(TimeSpan? retryAfter, int statusCode, string? reasonPhrase)
+ : base(statusCode, reasonPhrase) => RetryAfter = retryAfter;
+
+ /// How long to wait before retrying, if the server supplied the header.
+ public TimeSpan? RetryAfter { get; }
+ }
+}
+```
+
+All HTTP-level outcomes — including 5xx — are returned as subtypes. Genuine
+transport failures (network errors, DNS, timeout) still surface as
+`HttpRequestException` from the underlying `HttpClient` and are not caught by
+this library.
+
+### How consumers use this
+
+**Simple happy path** — just grab `.Data` and let it throw on failure:
+
+```csharp
+var company = (await client.GetCompanyProfileAsync("12345678")).Data;
+Console.WriteLine(company.CompanyName);
+```
+
+**Full branching** — pattern match when you need to handle each outcome:
+
+switch (response)
+{
+ case CompaniesHouseResponse.Success { Data: var company }:
+ Console.WriteLine(company.CompanyName);
+ break;
+
+ case CompaniesHouseResponse.NotFound:
+ Console.WriteLine("Company not found.");
+ break;
+
+ case CompaniesHouseResponse.RateLimited { RetryAfter: var delay }:
+ Console.WriteLine($"Rate limited. Retry after {delay}.");
+ break;
+
+ case CompaniesHouseResponse.Unauthorized:
+ Console.WriteLine("Check your API key.");
+ break;
+
+ case CompaniesHouseResponse.ServerError { RetryAfter: var delay, StatusCode: var code }:
+ Console.WriteLine($"Server error {code}. Retry after {delay}.");
+ break;
+
+ default:
+ Console.WriteLine($"Unexpected response: {response.StatusCode}");
+ break;
+}
+```
+
+### Decision: keep or rename the type?
+
+`CompaniesHouseClientResponse` → `CompaniesHouseResponse`.
+
+The `Client` infix adds no value and the shorter name reads more naturally as a
+return type. This is a deliberate breaking-change rename.
+
+## Scope
+
+### Breaking changes (expected — new major version)
+
+- `CompaniesHouseClientResponse` removed and replaced by
+ `CompaniesHouseResponse` with the subtype hierarchy above.
+- All sub-client interfaces change from
+ `Task>` to `Task>`.
+- All sub-client implementations updated.
+- `HttpResponseMessageExtensions.ToCompaniesHouseClientResponseAsync` renamed
+ and updated to return the appropriate subtype.
+
+### In-scope
+
+- New `CompaniesHouseResponse` abstract class with **six** sealed subtypes
+ (`Success`, `NotFound`, `RateLimited`, `Unauthorized`, `ClientError`,
+ `ServerError`).
+- Update `HttpResponseMessageExtensions` pipeline to build the correct subtype
+ from the `HttpResponseMessage`.
+- Update every `ICompaniesHouseX` interface and implementation to use the new
+ return type.
+- Tests: unit tests for each subtype (factory method / pipeline), plus scenario
+ tests verifying that a real 404 returns `NotFound`, a real success returns
+ `Success`, and so on.
+
+### Out of scope
+
+- No changes to request models, URI builders, or the `CompaniesHouseSettings`
+ hierarchy.
+
+## Tasks
+
+- [ ] Define `CompaniesHouseResponse` abstract class with the **six** subtypes in
+ `src/CompaniesHouse/CompaniesHouseResponse.cs`. Delete
+ `CompaniesHouseClientResponse.cs` and `CompaniesHouseApiException.cs`.
+- [ ] Update `HttpResponseMessageExtensions.ToCompaniesHouseResponseAsync` to
+ classify the response:
+ - 2xx → `Success` (deserialize body, expose full `Headers`)
+ - 404 → `NotFound`
+ - 429 → `RateLimited` (parse `Retry-After`)
+ - 401/403 → `Unauthorized`
+ - 5xx → `ServerError` (parse `Retry-After` — e.g. 503)
+ - other 4xx → `ClientError`
+- [ ] Update every `ICompaniesHouseX` interface return type.
+- [ ] Update every sub-client implementation return type (callers of the pipeline
+ need no other changes since the extension method does the heavy lifting).
+- [ ] Update unit tests in `CompaniesHouse.Tests` for the new type/subtypes.
+- [ ] Add scenario-level tests:
+ - Valid company number → `Success` with non-null `Data`.
+ - Nonexistent company number → `NotFound`.
+ - Confirm `RateLimited` surfaces `RetryAfter` (may need a mock/stub if
+ hitting the real API isn't reliable here).
+ - Confirm `ServerError` surfaces `RetryAfter` for a mocked 503.
+- [ ] Update the sample project (`samples/SampleProject`) to use pattern matching
+ on the new type.
+- [ ] Update `99-recurring-issues-backlog.md`: mark the #189 / #181 / #182
+ response-ergonomics row as resolved.
+
+## Design decisions
+
+- **`.Data` on the base throws for non-success** — provides a one-liner for the
+ happy path (`response.Data`) while making the failure explicit via
+ `InvalidOperationException` rather than silently returning null. Callers who
+ need to branch use pattern matching; callers who expect success use `.Data`
+ directly.
+- **Private base constructor** — prevents external subclassing; the compiler
+ knows the hierarchy is closed (exhaustiveness via `default` in switch).
+- **`T Data` not `T? Data`** — `Success.Data` is non-nullable; callers get a
+ compile-time guarantee that Data is present on success.
+- **5xx returns `ServerError`, not throws** — 5xx responses can carry a
+ `Retry-After` header (e.g. 503) and callers may have valid recovery logic.
+ Returning a type keeps all HTTP-level outcomes consistent. Genuine transport
+ failures (`HttpRequestException`) still propagate naturally from `HttpClient`.
+- **`CompaniesHouseApiException` removed** — all HTTP-level errors are now
+ represented as subtypes; the exception class is no longer needed.
+- **`Success` exposes full `HttpResponseHeaders`** — raw headers are surfaced
+ on `Success` so callers can inspect anything the API returns; parsed
+ well-known values (`RetryAfter`) are exposed as typed properties on the
+ relevant error subtypes.
+- **No non-generic `CompaniesHouseResponse` base** — callers always work with
+ the typed `CompaniesHouseResponse` variant; a non-generic base would add a
+ layer with no tangible benefit.
+- **`CompaniesHouseResponse` not `Result`** — domain-namespaced name is
+ clearer to consumers unfamiliar with result-type patterns.
+
+## Acceptance criteria
+
+- All existing tests pass with updated type names.
+- `CompaniesHouseClientResponse` and `CompaniesHouseApiException` do not
+ exist anywhere in the solution.
+- A switch over `CompaniesHouseResponse` without a `default`
+ branch produces a compiler warning (exhaustiveness via sealed hierarchy).
+- Scenario tests confirm: 404 → `NotFound`, 2xx → `Success` with non-null
+ `Data`, mocked 503 → `ServerError` with `RetryAfter` populated.
+
+## References
+
+- Issue #189 — "Ideas for changing the CompaniesHouseClientResponse class"
+- Issues #181/#182 — include StatusCode, ReasonPhrase, RetryAfter on failures
+- Plan `01` — core architecture (response wrapper originally introduced here)
diff --git a/.plans/outstanding/99-recurring-issues-backlog.md b/.plans/outstanding/99-recurring-issues-backlog.md
new file mode 100644
index 0000000..1f62997
--- /dev/null
+++ b/.plans/outstanding/99-recurring-issues-backlog.md
@@ -0,0 +1,91 @@
+# 99 — Recurring issues backlog (design validation)
+
+**Status:** outstanding (keep open as a checklist)
+**Purpose:** a living record of the real-world pain points the v-next design must
+eliminate. Validate each plan against this list; tick items off as the design
+provably handles them.
+
+## The dominant class: new enum values break deserialization
+
+By far the most common bug report. Every one of these is "Companies House
+returned a string my client's enum didn't know about, and it threw."
+
+- #168 — `ArgumentException: Requested value 'debenture'` (filing subcategory)
+- #185 / #186 — new company statuses & types
+- #184 — company type `registered-overseas-entity`
+- #197 / #198 — officer role `managing-officer` and other new roles
+- #200 / #201 — PSC deserialization / missing enum values
+- #209 / #210 — new filing category/subcategory values
+- #218 / #219 — filing subcategory `investment-company`
+
+**Design answer:** string-backed value types (plan `03`) + source generator from
+`api-enumerations` (plans `04`/`05`). Unknown values **never throw** and the raw
+string is preserved. ✅ when: a scenario test deserializes an unknown value for
+every enum-ish field without throwing.
+
+## Second class: missing / mistyped fields on responses
+
+- #205 — SIC codes
+- #206 / #207 — `total_results` missing on officers
+- #211 — `total_results` missing on PSC
+- #212 — SearchAll `total_results`/`items_per_page`/`start_index` typed as
+ string, should be int
+- #217 — `foreign_company_details` missing on company profile
+- #221 / #222 — `person_number` missing on officers
+- #155 / #173 — PSC `identification`
+- #179 — company address
+- #214 — PSC breaking data changes
+
+**Design answer:** rebuild every response model faithfully from the current docs
+(plans `06`–`09`), with `NumberHandling.AllowReadingFromString` for CH's
+string-numbers (plan `01`). ✅ when: each endpoint's model is doc-complete and
+has a deserialization test over a real payload.
+
+## Third class: raw value / observability
+
+- #156 — consumers want the raw string, not a mapped/swallowed value
+
+**Design answer:** value types keep `.Value` (the raw string) always. ✅ when:
+`.Value` returns the exact wire string for unknown values.
+
+## Response & error ergonomics
+
+- #181 / #182 — include StatusCode, ReasonPhrase, RetryAfter on failures
+- #189 — redesign `CompaniesHouseClientResponse`
+- #202 — auth discrepancies
+
+**Design answer:** redesigned response wrapper carrying transport metadata
+(plan `01`). ✅ when: a 429 surfaces `RetryAfter`; non-2xx surfaces status/headers.
+
+## Serialization & dependencies
+
+- #188 — move to `System.Text.Json`
+- #176 / #177 / #178 / #195 / #196 — endless Newtonsoft.Json version bumps
+
+**Design answer:** STJ everywhere, Newtonsoft removed entirely (plans `00`/`01`).
+✅ when: no `Newtonsoft.Json` reference anywhere in the repo.
+
+## DI / configuration
+
+- #190 / #193 — use `services.TryAdd*` (done — preserve)
+- #192 / #194 — pull the API key from any location
+- (v-next) move to `IOptions<>` with config binding + validation
+
+**Design answer:** `IApiKeyProvider` abstraction + `IOptions<>` pipeline
+(plan `02`). ✅ when: a custom `IApiKeyProvider` can be registered and the key
+can be bound from `IConfiguration`.
+
+## Feature gaps
+
+- #216 / #220 — advanced search not implemented → now first-class (plan `06`)
+- #165 / #166 — get individual officer appointment (plan `08`)
+- #163 / #164 — registered office address (plan `09`)
+- #180 — sandbox/test API support (configurable base URI — plans `01`/`02`)
+- #213 / #215 — optional date formatting on descriptable types
+- #169 / #171 — keep the `OfficerId` computed property (plan `08`)
+
+## How to use this file
+
+When closing out a plan, re-read the relevant class above and confirm the design
+demonstrably handles it (ideally with a test named after the issue). This file
+is the "have we actually fixed the recurring pain?" gate for the release.
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..8c7862f
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,159 @@
+# AGENTS.md
+
+Guidance for AI agents (and humans) working in this repository. Read this
+before making changes, then read the relevant plan in `.plans/outstanding/`.
+
+## What this project is
+
+`CompaniesHouse.NET` is a .NET client SDK for the
+[Companies House Public Data API](https://developer-specs.company-information.service.gov.uk/companies-house-public-data-api/reference).
+It is published as two NuGet packages:
+
+- **`CompaniesHouse`** — the core client (`CompaniesHouseClient`) and all
+ request/response models.
+- **`CompaniesHouse.Extensions.Microsoft.DependencyInjection`** — DI helpers
+ for registering the client with `IServiceCollection`.
+
+## Current state: a v-next rewrite
+
+We are building a **new major version** on the `prerelease` branch. Breaking
+changes are expected and welcome. The old surface lives on `master` and can be
+referenced for behaviour, but we are rebuilding the client endpoint-by-endpoint
+from the official API documentation rather than porting the old code verbatim.
+
+**The single source of truth for the work is `.plans/`.** Do not freelance a
+large redesign — pick up an outstanding plan, refine it if needed, and execute
+it.
+
+## Non-negotiable design decisions
+
+These are settled for the new major version. Do not reverse them without
+updating the relevant plan and calling it out explicitly.
+
+1. **Multi-target `net8.0;net9.0;net10.0`.** No `netstandard`, no `net45`. Drop
+ the `Microsoft.NETFramework.ReferenceAssemblies` and `Microsoft.Net.Http`
+ references.
+2. **`System.Text.Json` only.** Remove every reference to `Newtonsoft.Json`
+ (see issue #188). No new dependency on Json.NET in any project, including
+ tests.
+3. **No plain C# `enum`s on the wire.** Every API "enum" is modelled as a
+ **string-backed `readonly record struct`** that preserves the raw value and
+ never throws on an unrecognised value. See
+ `.plans/outstanding/03-string-backed-value-types.md` and the design blog
+ post: .
+4. **Enum values are generated, not hand-written.** A Roslyn **source
+ generator** produces the string-backed types from the Companies House
+ [`api-enumerations`](https://github.com/companieshouse/api-enumerations)
+ YAML (pulled in as a git submodule) plus our own local "extra" lists. We
+ ship a new package version to pick up new values — we do not hand-edit
+ generated types. See plans `04` and `05`.
+5. **`CompaniesHouseClient` stays the entry point.** Every capability hangs off
+ it as its own focused sub-client (e.g. search, company profile, officers),
+ each behind its own interface, exactly as today.
+6. **DI uses `IOptions<>`.** The DI package uses `AddOptions`,
+ `IConfiguration` binding and validation, with overloads to configure the
+ client several ways. See `.plans/outstanding/02-di-extensions-ioptions.md`.
+7. **`nullable` reference types enabled** across all projects.
+8. **Test stack: xUnit + Shouldly.** No NUnit, no FluentAssertions (license
+ changed to a paid tier from v8). Use `[Fact]`/`[Theory]`/`[MemberData]` and
+ `IAsyncLifetime` for async setup/teardown; assert with Shouldly's
+ `.ShouldBe(...)` family. For deep object-graph comparisons against test
+ fixtures that hold raw wire strings, use the repo's own
+ `EquivalencyAssertionExtensions.ShouldBeEquivalentTo(...)` helper in
+ `CompaniesHouse.Tests` (bridges enum <-> wire string, no FluentAssertions
+ `IEquivalencyStep` needed).
+
+## Repository layout
+
+```
+src/
+ CompaniesHouse/ core client + models
+ CompaniesHouse.Extensions.Microsoft.DependencyInjection/ DI helpers
+ (planned) CompaniesHouse.SourceGenerator/ enum value-type generator
+tests/
+ CompaniesHouse.Tests/ unit tests
+ CompaniesHouse.IntegrationTests/ hit the real API (needs key)
+ CompaniesHouse.ScenarioTests/ end-to-end behaviour
+ CompaniesHouse.Extensions.*.Tests/ DI tests
+samples/SampleProject/ runnable usage sample
+external/api-enumerations/ (planned) git submodule
+spec/swagger.json local CH OpenAPI 2.0 entrypoint
+spec/upstream/developer-specs.company-information.service.gov.uk/... vendored $ref specs
+CompaniesHouse.slnx solution (XML .slnx format)
+.plans/ the work breakdown (read this)
+```
+
+`spec/swagger.json` is now the root local spec file. Its `$ref` graph is
+rewritten to local paths under `spec/upstream/` so spec-driven work (for
+example, model nullability decisions) can run offline and deterministically.
+
+## Conventions
+
+- **File-scoped namespaces**, `ImplicitUsings` enabled, `LangVersion` latest.
+- **Warnings are errors** (`TreatWarningsAsErrors=true`) — keep the build clean.
+- One public type per file; interface `IThing` lives next to `Thing`.
+- URLs are built with small, testable **URI builder** types (see
+ `src/CompaniesHouse/UriBuilders`). Keep this pattern for new endpoints.
+- Async methods take a `CancellationToken` (defaulted) and end in `Async`.
+- JSON property names come from the API (snake_case); map with
+ `[JsonPropertyName(...)]` or a snake_case naming policy — never rename the
+ wire contract.
+- Prefer **central package management**: versions live in
+ `Directory.Packages.props`, not in individual `.csproj` files.
+
+## Build, test, format
+
+Run from the repository root.
+
+```powershell
+dotnet restore
+dotnet build -c Release
+dotnet test -c Release # unit + scenario tests
+dotnet format --verify-no-changes # style gate
+```
+
+Integration tests need a Companies House API key in the `api_key` environment
+variable and are skipped/failed without one — do not treat their absence as a
+regression when working offline.
+
+## Working agreement for agents
+
+- **Pick up a plan** from `.plans/outstanding/`. Work in the order implied by
+ the numeric prefixes (foundation first) unless the plan says otherwise.
+- **Keep changes surgical and endpoint-scoped.** Build the client up
+ gradually; do not rewrite everything in one pass.
+- **Update the plan as you learn.** Plans are living documents — refine tasks,
+ record decisions, and note open questions.
+- **When a plan is fully delivered and verified, move its file** from
+ `.plans/outstanding/` to `.plans/completed/` in the same change.
+- **Design for the recurring issues.** A huge share of historical bug reports
+ are "new enum value broke deserialisation" (#168, #185, #186, #197, #200,
+ #201, #209, #218) and "missing field on a response" (#205, #206, #211, #212,
+ #217, #221). The string-backed value types and generator exist to kill the
+ first class entirely; model responses faithfully from the docs to avoid the
+ second.
+- **Cite your sources.** Reference the specific API doc page and/or GitHub
+ issue in code comments and PRs when a decision is non-obvious.
+
+## Key references
+
+- API reference:
+- Enumerations repo:
+- Enum design rationale:
+- Issue tracker:
+
+## Release process
+
+On push to `master` or `prerelease`, the CI workflow automatically:
+1. Builds and packs both NuGet packages.
+2. Validates package metadata (README presence, nuspec readme tag).
+3. Pushes packages to NuGet.org.
+4. **Creates a GitHub release** with:
+ - Direct links to each package on NuGet.org (`nuget.org/packages/{PackageId}/{Version}`)
+ - Copy-paste `dotnet add package` commands for both packages
+ - Downloadable `.nupkg` and `.snupkg` files as release assets
+
+**Maintainers:** you do not need to manually write release notes or link to NuGet. The workflow handles it automatically.
+
+## Commits
+Commit in small amounts with a summary of what work we're building and not include the co-authorized by, however, do not push! Don't commit the .plans folder or the AGENTS.md
\ No newline at end of file
diff --git a/CompaniesHouse.sln b/CompaniesHouse.sln
deleted file mode 100644
index f9576c0..0000000
--- a/CompaniesHouse.sln
+++ /dev/null
@@ -1,61 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.30523.141
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompaniesHouse.Tests", "tests\CompaniesHouse.Tests\CompaniesHouse.Tests.csproj", "{BC825074-5662-421D-A849-FD94158F3029}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompaniesHouse.IntegrationTests", "tests\CompaniesHouse.IntegrationTests\CompaniesHouse.IntegrationTests.csproj", "{6B83B8C2-9DA6-42D5-BB32-BD0C6FDFC14D}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompaniesHouse", "src\CompaniesHouse\CompaniesHouse.csproj", "{9639747A-C49F-42E9-85A4-41FCBFCB7A16}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompaniesHouse.ScenarioTests", "tests\CompaniesHouse.ScenarioTests\CompaniesHouse.ScenarioTests.csproj", "{E1DA350A-FC73-4999-9B02-CBF8538945C9}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleProject", "samples\SampleProject\SampleProject.csproj", "{4F078B5D-05F5-4134-9B8F-1AC43BFCFD7E}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CompaniesHouse.Extensions.Microsoft.DependencyInjection", "src\CompaniesHouse.Extensions.Microsoft.DependencyInjection\CompaniesHouse.Extensions.Microsoft.DependencyInjection.csproj", "{5C6AC4CD-8E8B-4700-B01B-7B57C74AE791}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests", "tests\CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests\CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests.csproj", "{011662B7-5E03-4E6A-BAEA-B5C3FE169D3F}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {BC825074-5662-421D-A849-FD94158F3029}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {BC825074-5662-421D-A849-FD94158F3029}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {BC825074-5662-421D-A849-FD94158F3029}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {BC825074-5662-421D-A849-FD94158F3029}.Release|Any CPU.Build.0 = Release|Any CPU
- {6B83B8C2-9DA6-42D5-BB32-BD0C6FDFC14D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {6B83B8C2-9DA6-42D5-BB32-BD0C6FDFC14D}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {6B83B8C2-9DA6-42D5-BB32-BD0C6FDFC14D}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {6B83B8C2-9DA6-42D5-BB32-BD0C6FDFC14D}.Release|Any CPU.Build.0 = Release|Any CPU
- {9639747A-C49F-42E9-85A4-41FCBFCB7A16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {9639747A-C49F-42E9-85A4-41FCBFCB7A16}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {9639747A-C49F-42E9-85A4-41FCBFCB7A16}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {9639747A-C49F-42E9-85A4-41FCBFCB7A16}.Release|Any CPU.Build.0 = Release|Any CPU
- {E1DA350A-FC73-4999-9B02-CBF8538945C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {E1DA350A-FC73-4999-9B02-CBF8538945C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {E1DA350A-FC73-4999-9B02-CBF8538945C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {E1DA350A-FC73-4999-9B02-CBF8538945C9}.Release|Any CPU.Build.0 = Release|Any CPU
- {4F078B5D-05F5-4134-9B8F-1AC43BFCFD7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {4F078B5D-05F5-4134-9B8F-1AC43BFCFD7E}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {4F078B5D-05F5-4134-9B8F-1AC43BFCFD7E}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {4F078B5D-05F5-4134-9B8F-1AC43BFCFD7E}.Release|Any CPU.Build.0 = Release|Any CPU
- {5C6AC4CD-8E8B-4700-B01B-7B57C74AE791}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {5C6AC4CD-8E8B-4700-B01B-7B57C74AE791}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {5C6AC4CD-8E8B-4700-B01B-7B57C74AE791}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {5C6AC4CD-8E8B-4700-B01B-7B57C74AE791}.Release|Any CPU.Build.0 = Release|Any CPU
- {011662B7-5E03-4E6A-BAEA-B5C3FE169D3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {011662B7-5E03-4E6A-BAEA-B5C3FE169D3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {011662B7-5E03-4E6A-BAEA-B5C3FE169D3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {011662B7-5E03-4E6A-BAEA-B5C3FE169D3F}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {1CF7FB6A-FF49-463C-95A9-C35E7F97B96E}
- EndGlobalSection
-EndGlobal
diff --git a/CompaniesHouse.slnx b/CompaniesHouse.slnx
new file mode 100644
index 0000000..8084acf
--- /dev/null
+++ b/CompaniesHouse.slnx
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Directory.Build.props b/Directory.Build.props
index 221bfca..8f4969c 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -3,13 +3,18 @@
true
latest
enable
+ enable
true
+
+ $(NoWarn);CS1591
Kevin Smith
Kevsoft
- Copyright © Kevsoft 2020
+ Copyright © Kevsoft
CompaniesHouse;Registrar;Kevsoft;API;REST;WebService
https://raw.githubusercontent.com/kevbite/CompaniesHouse.NET/master/companies-house.jpg
@@ -20,4 +25,12 @@
git
https://github.com/kevbite/CompaniesHouse.NET
+
+
+
+ true
+ true
+ true
+ true
+
\ No newline at end of file
diff --git a/Directory.Packages.props b/Directory.Packages.props
new file mode 100644
index 0000000..a8734d2
--- /dev/null
+++ b/Directory.Packages.props
@@ -0,0 +1,35 @@
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Dockerfile b/Dockerfile
index b8001b1..583df9c 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,22 +1,25 @@
+# syntax=docker/dockerfile:1
ARG CONFIGURATION="Release"
ARG NUGET_PACKAGE_VERSION="1.0.0"
-ARG COMPANIES_HOUSE_API_KEY
-FROM mcr.microsoft.com/dotnet/sdk:9.0 AS restore
+FROM mcr.microsoft.com/dotnet/sdk:10.0 AS restore
ARG CONFIGURATION
COPY ./*.props .
COPY ./*.targets .
-COPY ./*.sln .
+COPY ./global.json .
+COPY ./*.slnx .
COPY ./*.jpg .
COPY ./README.md .
COPY ./LICENSE .
COPY ./src/CompaniesHouse/*.csproj ./src/CompaniesHouse/
COPY ./src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/*.csproj ./src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/
+COPY ./src/CompaniesHouse.SourceGenerator/*.csproj ./src/CompaniesHouse.SourceGenerator/
COPY ./tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests/*.csproj ./tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests/
COPY ./tests/CompaniesHouse.IntegrationTests/*.csproj ./tests/CompaniesHouse.IntegrationTests/
COPY ./tests/CompaniesHouse.ScenarioTests/*.csproj ./tests/CompaniesHouse.ScenarioTests/
+COPY ./tests/CompaniesHouse.SourceGenerator.Tests/*.csproj ./tests/CompaniesHouse.SourceGenerator.Tests/
COPY ./tests/CompaniesHouse.Tests/*.csproj ./tests/CompaniesHouse.Tests/
COPY ./samples/SampleProject/*.csproj ./samples/SampleProject/
RUN dotnet restore
@@ -28,11 +31,14 @@ ARG NUGET_PACKAGE_VERSION
COPY ./src/ ./src/
COPY ./tests/ ./tests/
COPY ./samples/ ./samples/
-RUN dotnet build --configuration $CONFIGURATION
+COPY ./external/ ./external/
+COPY ./enumerations/ ./enumerations/
+RUN dotnet build --configuration $CONFIGURATION --no-restore
FROM build AS test
-ARG COMPANIES_HOUSE_API_KEY
-RUN dotnet test --logger trx --configuration $CONFIGURATION --no-build; exit 0
+RUN --mount=type=secret,id=companies_house_api_key,required=false \
+ export COMPANIES_HOUSE_API_KEY="$(cat /run/secrets/companies_house_api_key 2>/dev/null || true)" && \
+ dotnet test --logger trx --configuration $CONFIGURATION --no-build
FROM build AS pack
RUN mkdir -p artifacts
diff --git a/MIGRATION.md b/MIGRATION.md
new file mode 100644
index 0000000..5d68415
--- /dev/null
+++ b/MIGRATION.md
@@ -0,0 +1,219 @@
+# Migration guide: v-next (breaking changes)
+
+This is a new major version of `CompaniesHouse.NET`. It is a deliberate,
+breaking rewrite - it does not attempt to be a drop-in replacement. This guide
+covers every breaking change with a before/after snippet so you can migrate
+call sites methodically.
+
+## Target frameworks
+
+**Before:** `net45`, `netstandard2.0` (or similar).
+**After:** `net8.0`, `net9.0`, `net10.0` only.
+
+If you're on .NET Framework or an older .NET/`netstandard` target, you'll need
+to stay on the previous major version, or upgrade your app to a supported TFM.
+
+## JSON: `Newtonsoft.Json` → `System.Text.Json`
+
+The client no longer depends on `Newtonsoft.Json` at all (see issue #188). All
+(de)serialization uses `System.Text.Json`.
+
+This mostly only matters if you had custom `JsonConverter`s or relied on
+`Newtonsoft`-specific behaviour (e.g. `JObject`/`JToken` on the response
+models, or `[JsonProperty]` attributes):
+
+```diff
+- using Newtonsoft.Json;
+- var json = JsonConvert.SerializeObject(profile);
++ using System.Text.Json;
++ using CompaniesHouse;
++ var json = JsonSerializer.Serialize(profile, CompaniesHouseJsonSerializerOptions.Default);
+```
+
+If you were deserializing raw API responses yourself, use
+`CompaniesHouseJsonSerializerOptions.Default` so enum value types and casing
+are handled consistently with the client.
+
+## Enums → string-backed value types
+
+This is the biggest behavioural change. Every API "enum" (company status,
+officer role, charge type, jurisdiction, ...) used to be a plain C# `enum`.
+It's now a **string-backed, readonly `record struct`** that never throws for
+an unrecognised value.
+
+**Before:**
+
+```csharp
+public enum CompanyStatus
+{
+ Active,
+ Dissolved,
+ // ... a fixed, hand-maintained list
+}
+
+switch (profile.CompanyStatus)
+{
+ case CompanyStatus.Active:
+ Console.WriteLine("is active");
+ break;
+ case CompanyStatus.Dissolved:
+ Console.WriteLine("is dissolved");
+ break;
+ default:
+ // Companies House adding a new status value could throw a
+ // JsonSerializationException during deserialization, or silently
+ // map to an unrelated member, depending on the old converter.
+ break;
+}
+```
+
+**After:**
+
+```csharp
+var description = profile.CompanyStatus switch
+{
+ var s when s == CompanyStatus.Active => "is active",
+ var s when s == CompanyStatus.Dissolved => "is dissolved",
+ var s when s.IsKnown => s.Description, // any other value this library recognises
+ var s => $"unrecognised status: {s.Value}", // never throws, even for brand-new values
+};
+```
+
+Key API differences to update at each call site:
+
+- Replace `EnumType.Member` usages with the equivalent static member on the
+ value type (e.g. `CompanyStatus.Active` still works, but it's a value not an
+ `enum` member - `==`/`!=` work as expected via `record struct` equality).
+- Replace `switch` statements on the type itself with pattern matching against
+ equality (`s == CompanyStatus.Active`), since the value type isn't a closed
+ set of cases.
+- Anywhere you called `.ToString()` expecting the C# member name (e.g.
+ `"Active"`), note that `ToString()` now returns the **raw wire value**
+ (e.g. `"active"`); use `.Description` for a friendly name.
+- Anywhere you relied on `Enum.Parse`/`Enum.TryParse`, construct the value type
+ directly from the wire string instead: `new CompanyStatus("active")`.
+
+See the [README's enum section](README.md#enumvalue-type-handling) and the
+[design rationale](https://kevsoft.net/2026/06/28/enums-in-api-contracts.html)
+for more detail.
+
+## Response type: discriminated union
+
+**Before:** the response wrapper exposed only the deserialized body:
+
+```csharp
+var profile = await client.GetCompanyProfileAsync(companyNumber);
+// profile was the data itself, or null
+```
+
+**After:** every client method returns `CompaniesHouseResponse` — a sealed
+type hierarchy. The concrete subtype tells you exactly what happened:
+
+```diff
+- var profile = await client.GetCompanyProfileAsync(companyNumber);
+- if (profile == null)
+- return; // 404 or some other error
+- Console.WriteLine(profile.CompanyName);
+
++ // Happy path: .Data throws InvalidOperationException on non-success
++ var company = (await client.GetCompanyProfileAsync(companyNumber)).Data;
++ Console.WriteLine(company.CompanyName);
+
++ // Or pattern-match for fine-grained handling
++ var result = await client.GetCompanyProfileAsync(companyNumber);
++ var message = result switch
++ {
++ CompaniesHouseResponse.Success { Data: var c } => c.CompanyName,
++ CompaniesHouseResponse.NotFound => "not found",
++ CompaniesHouseResponse.RateLimited { RetryAfter: var d } => $"retry after {d}",
++ CompaniesHouseResponse.Unauthorized => "check API key",
++ CompaniesHouseResponse.ServerError { StatusCode: var s } => $"server error {s}",
++ _ => $"HTTP {result.StatusCode}",
++ };
+```
+
+All subtypes expose `StatusCode` and `ReasonPhrase`. `Success` additionally
+exposes the full `HttpResponseHeaders`. `RateLimited` and `ServerError` expose
+`RetryAfter` (resolves issues #181/#182). Transport-level failures
+(`HttpRequestException`) are not caught — they propagate as normal exceptions.
+
+`CompaniesHouseApiException` has been removed. If you were catching it for 5xx
+handling, switch to matching on `ServerError` instead:
+
+```diff
+- catch (CompaniesHouseApiException ex) when (ex.StatusCode == 503)
+- {
+- await Task.Delay(ex.RetryAfter ?? TimeSpan.FromSeconds(30));
+- }
+
++ if (result is CompaniesHouseResponse.ServerError { RetryAfter: var delay })
++ await Task.Delay(delay ?? TimeSpan.FromSeconds(30));
+```
+
+## Default base URI change
+
+**Before:** `https://api.companieshouse.gov.uk/`
+(or a similar legacy host, depending on version).
+
+**After:** `https://api.company-information.service.gov.uk/`
+(`CompaniesHouseUris.Default`).
+
+If you previously passed a base URI explicitly, no change is needed. If you
+relied on the implicit default, verify it now resolves to the new host - your
+existing API key works against both.
+
+## DI package changes
+
+**Before:**
+
+```csharp
+services.AddCompaniesHouseClient("Your API Key");
+```
+
+**After:** the same call still works, plus new overloads built on
+`IOptions`:
+
+```csharp
+// Still works
+services.AddCompaniesHouseClient(apiKey);
+
+// New: configure via a delegate
+services.AddCompaniesHouseClient(options =>
+{
+ options.ApiKey = apiKey;
+ options.BaseUri = CompaniesHouseUris.Default;
+});
+
+// New: bind from IConfiguration
+services.AddCompaniesHouseClient(configuration); // reads the "CompaniesHouse" section
+
+// New: customise the underlying IHttpClientBuilder
+services.AddCompaniesHouseClient(apiKey, builder => builder.AddStandardResilienceHandler());
+```
+
+Document-endpoint registration follows the same pattern via
+`AddCompaniesHouseDocumentClient`, reading from the `CompaniesHouseDocument`
+configuration section by default.
+
+## Test stack (if you forked/contributed tests)
+
+Tests moved from NUnit + FluentAssertions to **xUnit + Shouldly** (Fluent
+Assertions' license changed to a paid tier from v8):
+
+```diff
+- [Test]
+- public void Should_return_active_status()
+- {
+- result.CompanyStatus.Should().Be(CompanyStatus.Active);
+- }
++ [Fact]
++ public void Should_return_active_status()
++ {
++ result.CompanyStatus.ShouldBe(CompanyStatus.Active);
++ }
+```
+
+## Getting help
+
+If you hit a migration issue not covered here, please open an issue at
+.
diff --git a/README.md b/README.md
index c515aa5..58aaf69 100644
--- a/README.md
+++ b/README.md
@@ -1,175 +1,330 @@
# CompaniesHouse.NET
-A simple .NET client wrapper for CompaniesHouse API.
+A .NET client for the [Companies House Public Data API](https://developer-specs.company-information.service.gov.uk/companies-house-public-data-api/reference).
[](https://www.nuget.org/packages/CompaniesHouse)
[](https://www.nuget.org/packages/CompaniesHouse)
-[](https://ci.appveyor.com/project/kevbite/companieshouse-net/branch/master)
+[](https://github.com/kevbite/CompaniesHouse.NET/actions/workflows/continuous-integration-workflow.yml)
-## Getting Started
+> **Upgrading from an earlier version?** This is a major, deliberately breaking
+> rewrite. See [MIGRATION.md](MIGRATION.md) for the full list of changes and
+> before/after snippets.
-CompaniesHouse.NET can be installed via the package manager console by executing the following commandlet:
+## Installation
+
+Two NuGet packages are published:
```powershell
-PM> Install-Package CompaniesHouse
+# The core client and all request/response models
+dotnet add package CompaniesHouse
+
+# Optional: DI helpers for ASP.NET Core / generic-host apps
+dotnet add package CompaniesHouse.Extensions.Microsoft.DependencyInjection
```
-Once we have the package installed, we can then create a `CompaniesHouseSettings` with an API key, which can be created via the [CompaniesHouse API website](https://developer.company-information.service.gov.uk/manage-applications).
+Both packages multi-target `net8.0`, `net9.0` and `net10.0`.
+
+## Getting an API key
+
+Register an application on the
+[Companies House developer hub](https://developer.company-information.service.gov.uk/)
+to get an API key for the public data API.
+
+## Getting started
+
+### Constructing the client directly
```csharp
+using CompaniesHouse;
+
var settings = new CompaniesHouseSettings(apiKey);
+
+using var client = new CompaniesHouseClient(settings);
```
-We need to now create a `CompaniesHouseClient` - passing in the settings that we've just created.
+`CompaniesHouseClient` implements `IDisposable` - always dispose it (or wrap it
+in a `using` block) once you're done, since it owns an underlying `HttpClient`.
+
+You can also construct the client from your own `HttpClient` (useful in tests,
+or when you want full control over handlers/base address):
```csharp
-using(var client = new CompaniesHouseClient(settings))
-{
- // Do some work...
-}
+using var httpClient = new HttpClient { BaseAddress = CompaniesHouseUris.Default };
+httpClient.DefaultRequestHeaders.Authorization =
+ new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{apiKey}:")));
+
+using var client = new CompaniesHouseClient(httpClient);
```
-This is the object we'll use going forward for any interaction to the CompaniesHouse API, but don't forget to call `Dispose` after you've finish (or wrap in a `using` block).
+### Dependency injection
-### ASP.NET Core
+Install `CompaniesHouse.Extensions.Microsoft.DependencyInjection` and register
+the client on your `IServiceCollection`. Several overloads are available,
+built on `IOptions`:
-If you're using [ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/?view=aspnetcore-5.0) you can configure the IoC container with one simple extention method call. But first you'll need to install the [CompaniesHouse.Extensions.Microsoft.DependencyInjection](https://www.nuget.org/packages/CompaniesHouse.Extensions.Microsoft.DependencyInjection/) NuGet package.
+```csharp
+// Simplest - just an API key
+services.AddCompaniesHouseClient(apiKey);
-```powershell
-PM> Install-Package CompaniesHouse.Extensions.Microsoft.DependencyInjection
+// A custom base URI (e.g. against a sandbox/test host)
+services.AddCompaniesHouseClient(new Uri("https://api.company-information.service.gov.uk/"), apiKey);
+
+// Full control via a delegate
+services.AddCompaniesHouseClient(options =>
+{
+ options.ApiKey = apiKey;
+ options.BaseUri = CompaniesHouseUris.Default;
+});
+
+// Bind from IConfiguration (defaults to the "CompaniesHouse" section)
+services.AddCompaniesHouseClient(configuration);
+```
+
+Every overload also accepts an optional `configureHttpClientBuilder` delegate,
+letting you customise the underlying `IHttpClientBuilder` (e.g. to add Polly
+resilience handlers):
+
+```csharp
+services.AddCompaniesHouseClient(apiKey, builder => builder.AddStandardResilienceHandler());
```
-Once installed, in your [Startup class](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/startup?view=aspnetcore-5.0) in the `ConfigureServices` method, call the `AddCompaniesHouseClient` method on the `services` object.
+Once registered, inject `ICompaniesHouseClient` - the main facade interface -
+into your dependencies:
```csharp
-public void ConfigureServices(IServiceCollection services)
+public class MyPageModel(ICompaniesHouseClient client) : PageModel
{
// ...
- services.AddCompaniesHouseClient("Your API Key");
}
```
-This will then register a range of interfaces in to the IoC container that can be injected in to any of your dependancies. A list of these can be found in the [ServiceCollectionExtensionsTests](https://github.com/kevbite/CompaniesHouse.NET/blob/master/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests/ServiceCollectionExtensionsTests.cs#L17).
+Document endpoints (`GetDocumentMetadataAsync`/`DownloadDocumentAsync`) talk to
+a separate host and are registered independently via
+`AddCompaniesHouseDocumentClient`, with the same set of overloads.
-For example if we wanted to use the `ICompaniesHouseClient` which is the main facade interface, we could inject this in to our page model.
+### `IConfiguration` example
+
+```json
+{
+ "CompaniesHouse": {
+ "ApiKey": "your-api-key",
+ "BaseUri": "https://api.company-information.service.gov.uk/"
+ }
+}
+```
```csharp
-public class MyPageModel : PageModel
+services.AddCompaniesHouseClient(builder.Configuration);
+```
+
+## Enum/value-type handling
+
+Every "enum" in the Companies House API (company status, officer role, charge
+type, etc.) is modelled as a **string-backed, readonly `record struct`**
+rather than a plain C# `enum`. This is deliberate: Companies House regularly
+adds new wire values, and a plain `enum` throws (or silently defaults) the
+moment it sees one it doesn't recognise. See the
+[design rationale](https://kevsoft.net/2026/06/28/enums-in-api-contracts.html)
+for the full background.
+
+```csharp
+CompanyStatus status = companyProfile.CompanyStatus;
+
+status.Value; // the raw wire value, e.g. "active"
+status.HasValue; // false only for the default/absent value
+status.IsKnown; // true if this library recognises the value
+status.Description; // a friendly description for known values, e.g. "Active"
+```
+
+Compare against the generated static members (`CompanyStatus.Active`,
+`CompanyStatus.Dissolved`, ...) rather than raw strings, and always keep a
+fallback arm for values you don't recognise yet:
+
+```csharp
+var description = status switch
{
- private readonly ICompaniesHouseClient _client;
+ _ when status == CompanyStatus.Active => "is active",
+ _ when status == CompanyStatus.Dissolved => "is dissolved",
+ _ when status.IsKnown => status.Description,
+ _ => $"unrecognised status: {status.Value}", // never throws
+};
+```
- public Index2Model(ICompaniesHouseClient client)
- {
- _client = client;
- }
-}
+New values ship as a new minor version of the `CompaniesHouse` package (the
+value types are generated from the official
+[`api-enumerations`](https://github.com/companieshouse/api-enumerations) data)
+- you never need to hand-edit or wait on a code change to keep deserializing.
+
+## Reading responses
+
+Every client method returns a `CompaniesHouseResponse` — a discriminated
+union whose concrete subtype tells you exactly what happened:
+
+| Subtype | When | Extra property |
+|---|---|---|
+| `Success` | 2xx | `Data` (non-null), `Headers` |
+| `NotFound` | 404 | — |
+| `RateLimited` | 429 | `RetryAfter` |
+| `Unauthorized` | 401/403 | — |
+| `ClientError` | other 4xx | — |
+| `ServerError` | 5xx | `RetryAfter` |
+
+All subtypes expose `StatusCode` and `ReasonPhrase`. Transport failures (network
+errors, DNS, timeout) propagate as `HttpRequestException` from the underlying
+`HttpClient`.
+
+### Simple happy path
+
+Call `.Data` directly — it returns the deserialized body on `Success` and throws
+`InvalidOperationException` for every other subtype, so you never silently get
+`null`:
+
+```csharp
+var company = (await client.GetCompanyProfileAsync(companyNumber)).Data;
+Console.WriteLine(company.CompanyName);
```
-Under the hood this is using [typed clients](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-5.0#typed-clients) for the `HttpClient` used by CompaniesHouse.NET and it's also possible to use this package with any dependency injection framework that implements `Microsoft.Extensions.DependencyInjection.Abstractions`.
+### Full branching
+
+Use a switch expression when you need to handle specific outcomes:
+
+```csharp
+var result = await client.GetCompanyProfileAsync(companyNumber);
+
+var message = result switch
+{
+ CompaniesHouseResponse.Success { Data: var company } =>
+ $"Found: {company.CompanyName}",
+
+ CompaniesHouseResponse.NotFound =>
+ "Company not found.",
+
+ CompaniesHouseResponse.RateLimited { RetryAfter: var delay } =>
+ $"Rate limited — retry after {delay}.",
+
+ CompaniesHouseResponse.Unauthorized =>
+ "Check your API key.",
+
+ CompaniesHouseResponse.ServerError { StatusCode: var code, RetryAfter: var delay } =>
+ $"Server error {code} — retry after {delay}.",
+
+ _ => $"Unexpected response: {result.StatusCode}",
+};
+```
## Usage
### Searching for resources
-To search for a resource, we first need to create a `SearchRequest` with details of the search we require.
-
```csharp
-var request = new SearchRequest()
+var request = new SearchAllRequest
{
Query = "Jay2Base",
- StartIndex = 10,
+ StartIndex = 0,
ItemsPerPage = 10
};
-```
-
-We can then pass the `SearchRequest` object in to the `SearchAllAsync` method and await on the task, this will then return all related resources.
-```csharp
var result = await client.SearchAllAsync(request);
-foreach (var item in _result.Data.Items)
+foreach (var item in result.Data.Items)
{
// Do something...
}
```
-If we need to be more precise on the resources we require, we can then pass the request object in to the required search method, either `SearchCompanyAsync` or `SearchOfficerAsync` or `SearchDisqualifiedOfficerAsync` and await on the task.
+For a specific resource type, use `SearchCompanyAsync`, `SearchOfficerAsync`,
+`SearchDisqualifiedOfficerAsync`, `SearchCompaniesAlphabeticallyAsync`,
+`SearchDissolvedCompaniesAsync` or `AdvancedCompanySearchAsync` with the
+matching request type.
```csharp
-var result1 = await client.SearchCompanyAsync(request);
-
-var result2 = await client.SearchOfficerAsync(request);
-
-var result3 = await client.SearchDisqualifiedOfficerAsync(request);
+var companies = await client.SearchCompanyAsync(new SearchCompanyRequest { Query = "Jay2Base" });
+var officers = await client.SearchOfficerAsync(new SearchOfficerRequest { Query = "Jay2Base" });
+var disqualified = await client.SearchDisqualifiedOfficerAsync(new SearchDisqualifiedOfficerRequest { Query = "Jay2Base" });
```
### Getting a company profile
-To get a company profile, we pass a company number in to the `GetCompanyProfileAsync` method and await on the task.
-
```csharp
var result = await client.GetCompanyProfileAsync("10440441");
```
-If there was no match for that company number then `null` will be returned.
-
-### Getting company officer list
+`result` is a `NotFound` subtype if there was no match for that company number.
-To get a list of officers for a company, we pass a company number in to the `GetOfficersAsync` method and await on the task.
+### Getting the company officer list
```csharp
var result = await client.GetOfficersAsync("03977902");
+
+// Optionally page the results
+var page = await client.GetOfficersAsync("03977902", startIndex: 10, pageSize: 10);
```
-We can also pass in some optional parameters of `startIndex` and `pageSize` which will allow us to page the results.
+A single officer appointment can be fetched directly:
```csharp
-var result = await client.GetOfficersAsync("03977902", 10, 10);
+var officer = await client.GetOfficerByAppointmentIdAsync("03977902", appointmentId);
```
-### Getting company filing history list
-
-To get a list of the filing history for a company, we can pass a company number to the `GetCompanyFilingHistoryAsync` method and await on the task.
+### Getting officer appointments
```csharp
-var result = await client.GetCompanyFilingHistoryAsync("10440441");
+var result = await client.GetAppointmentsAsync(officerId, startIndex: 0, pageSize: 25);
```
-We can also pass in some optional parameters of `startIndex` and `pageSize` which will allow us to page the results.
+### Getting the company filing history
```csharp
-var result = await client.GetCompanyFilingHistoryAsync("10440441", 10, 10);
+var result = await client.GetCompanyFilingHistoryAsync("10440441", startIndex: 0, pageSize: 25);
+
+var item = await client.GetFilingHistoryByTransactionAsync("10440441", transactionId);
```
### Getting company insolvency information
-To get the insolvency information for a company, we can pass a company number to the `GetCompanyInsolvencyInformationAsync` method and await on the task.
-
```csharp
var result = await client.GetCompanyInsolvencyInformationAsync("10440441");
```
-If there was no insolvency information for the given company number then `null` will be returned.
+`result` is a `NotFound` subtype if there is no insolvency information for the company.
-### Getting document metadata information
+### Getting persons with significant control
-To get the metadata for a document, we can pass document id to the `GetDocumentMetadataAsync` method and await on the task.
+```csharp
+var result = await client.GetPersonsWithSignificantControlAsync("10440441", startIndex: 0, pageSize: 25);
+```
+
+### Getting charges
```csharp
-var result = await client.GetDocumentMetadataAsync("FIxRR8teCKodjkBLRDHv2Cb8y0-nQ7T5G3BEXfWtOu4");
+var charges = await client.GetChargesListAsync("10440441", startIndex: 0, pageSize: 25);
+
+var charge = await client.GetChargeByIdAsync("10440441", chargeId);
```
-If there was no document metadata for the given document id then `null` will be returned.
+### Getting the registered office address
-### Downloading a document
+```csharp
+var result = await client.GetRegisteredOfficeAddress("10440441");
+```
-To download a document, we can pass document id to the `DownloadDocumentAsync` method and await on the task.
+### Getting document metadata and downloading a document
```csharp
-var result = await client.DownloadDocumentAsync("FIxRR8teCKodjkBLRDHv2Cb8y0-nQ7T5G3BEXfWtOu4");
+var metadata = await client.GetDocumentMetadataAsync("FIxRR8teCKodjkBLRDHv2Cb8y0-nQ7T5G3BEXfWtOu4");
+
+var document = await client.DownloadDocumentAsync("FIxRR8teCKodjkBLRDHv2Cb8y0-nQ7T5G3BEXfWtOu4");
```
-If there was no document for the given document id then `null` will be returned.
+`metadata`/`document` is a `NotFound` subtype if there was no metadata/document for the given id.
+
+More endpoints land progressively - see `.plans/` for what's in flight.
+
+## Sample project
+
+A runnable end-to-end example, covering direct construction, DI registration,
+search, company profile, officers, and gracefully handling an unrecognised
+enum value, lives in [`samples/SampleProject`](samples/SampleProject).
## Contributing
@@ -177,9 +332,24 @@ If there was no document for the given document id then `null` will be returned.
1. Hack!
1. Pull Request
+See [AGENTS.md](AGENTS.md) for repository conventions, build/test commands and
+the design decisions behind the v-next rewrite.
+
+## Maintainer release notes
+
+NuGet publishing is driven by the CI Docker build, which produces the final
+`.nupkg` artifacts in `./artifacts`. Before push-to-NuGet, CI validates that
+each package contains `README.md` and has nuspec `README.md`
+metadata so NuGet.org renders the project README correctly.
-## Running Unit tests
+## Running tests
-In order for the integration tests to run, you need an API Key from [CompaniesHouse API website](https://developer.companieshouse.gov.uk/developer/applications)
-Setup API Key in an environment variable called "api_key", and then run the tests.
+```powershell
+dotnet restore
+dotnet build -c Release
+dotnet test -c Release
+```
+Integration tests hit the real Companies House API and need an API key in the
+`COMPANIES_HOUSE_API_KEY` environment variable - they're skipped/fail without
+one, which is expected when working offline.
diff --git a/enumerations/extra/README.md b/enumerations/extra/README.md
new file mode 100644
index 0000000..2dcd757
--- /dev/null
+++ b/enumerations/extra/README.md
@@ -0,0 +1,55 @@
+# `enumerations/extra/`
+
+This folder is a repo-local overlay on top of the
+[`companieshouse/api-enumerations`](https://github.com/companieshouse/api-enumerations)
+git submodule at `external/api-enumerations/`. It lets us:
+
+1. **Add values Companies House haven't published yet** (or have used in the
+ live API before updating their own reference data).
+2. **Define library-only enum groups** that don't exist upstream at all.
+
+The source generator (plan `04`) reads YAML from **both** locations and merges
+them per top-level group key:
+
+- The submodule (`external/api-enumerations/*.yml`) is read first.
+- Files in this folder (`enumerations/extra/*.yml`) are read second and merged
+ **on top**: for a given group (e.g. `company_status`), any wire-value key
+ present in an extras file **overrides** the submodule's entry for that key;
+ keys not present in the submodule are **appended**.
+- You may add entirely new group keys here that don't exist upstream at all —
+ they behave like any other generated group.
+
+## File format
+
+Same shape as the upstream files — a YAML mapping of group name to a mapping
+of wire value to human-readable description:
+
+```yaml
+group_name:
+ 'wire-value' : "Friendly description"
+```
+
+## Example
+
+`company_status.yml` in this folder adds the description for `closed-on`,
+which the upstream `constants.yml` `company_status` group is missing (all
+other `company_status` entries continue to come from the submodule):
+
+```yaml
+company_status:
+ 'closed-on' : "Closed On"
+```
+
+## Refreshing the upstream submodule
+
+```powershell
+git submodule update --remote external/api-enumerations
+git add external/api-enumerations
+git commit -m "Bump api-enumerations submodule"
+```
+
+A scheduled workflow (`.github/workflows/bump-api-enumerations.yml`) does this
+automatically once a month and opens a pull request for review — new upstream
+values still only take effect once that PR is merged and the package is
+rebuilt (see plan `04`: generation happens at build time in this repo, not in
+consumers).
diff --git a/enumerations/extra/charges.yml b/enumerations/extra/charges.yml
new file mode 100644
index 0000000..10e9b55
--- /dev/null
+++ b/enumerations/extra/charges.yml
@@ -0,0 +1,29 @@
+charge_status:
+ 'outstanding': ''
+ 'fully-satisfied': ''
+ 'part-satisfied': ''
+ 'satisfied': ''
+
+classification_charge_type:
+ 'charge-description': ''
+ 'nature-of-charge': ''
+
+particular_type:
+ 'short-particulars': ''
+ 'charged-property-description': ''
+ 'charged-property-or-undertaking-description': ''
+ 'brief-description': ''
+
+secured_detail_type:
+ 'amount-secured': ''
+ 'obligations-secured': ''
+
+assets_ceased_released:
+ 'property-ceased-to-belong': ''
+ 'part-property-release-and-ceased-to-belong': ''
+ 'part-property-released': ''
+ 'part-property-ceased-to-belong': ''
+ 'whole-property-released': ''
+ 'multiple-filings': ''
+ 'whole-property-released-and-ceased-to-belong': ''
+
diff --git a/enumerations/extra/company_status.yml b/enumerations/extra/company_status.yml
new file mode 100644
index 0000000..4884e2f
--- /dev/null
+++ b/enumerations/extra/company_status.yml
@@ -0,0 +1,2 @@
+company_status:
+ 'closed-on' : "Closed On"
diff --git a/enumerations/extra/filing.yml b/enumerations/extra/filing.yml
new file mode 100644
index 0000000..d50c2a8
--- /dev/null
+++ b/enumerations/extra/filing.yml
@@ -0,0 +1,81 @@
+filing_history_status:
+ 'filing-history-available': ''
+ 'filing-history-not-available-invalid-format': ''
+ 'filing-history-available-no-images-limited-partnership-from-1988': ''
+ 'filing-history-available-assurance-company-before-2004': ''
+ 'filing-history-available-limited-partnership-from-2014': ''
+ 'filing-history-not-available-industrial-and-provident-society': ''
+ 'filing-history-not-available-limited-partnership-before-1988': ''
+ 'filing-history-not-available-royal-charter': ''
+ 'filing-history-not-available-scottish-industrial-and-provident-society': ''
+ 'filing-history-not-available-northern-ireland-industrial-and-provident-society': ''
+ 'filing-history-not-available-unknown-prefix': ''
+
+filing_category:
+ 'auditors': ''
+ 'accounts': ''
+ 'address': ''
+ 'annual-return': ''
+ 'capital': ''
+ 'gazette': ''
+ 'change-of-name': ''
+ 'incorporation': ''
+ 'liquidation': ''
+ 'miscellaneous': ''
+ 'mortgage': ''
+ 'officers': ''
+ 'resolution': ''
+ 'change-of-constitution': ''
+ 'document-replacement': ''
+ 'insolvency': ''
+ 'confirmation-statement': ''
+ 'persons-with-significant-control': ''
+ 'historical': ''
+ 'dissolution': ''
+ 'restoration': ''
+ 'return': ''
+ 'other': ''
+ 'court-order': ''
+ 'reregistration': ''
+ 'certificate': ''
+
+filing_subcategory:
+ 'annual-return': ''
+ 'resolution': ''
+ 'change': ''
+ 'create': ''
+ 'certificate': ''
+ 'appointments': ''
+ 'satisfy': ''
+ 'termination': ''
+ 'release-cease': ''
+ 'voluntary': ''
+ 'administration': ''
+ 'compulsory': ''
+ 'court-order': ''
+ 'other': ''
+ 'notifications': ''
+ 'officers': ''
+ 'document-replacement': ''
+ 'statements': ''
+ 'voluntary-arrangement': ''
+ 'alter': ''
+ 'register': ''
+ 'receiver': ''
+ 'voluntary-arrangement-moratoria': ''
+ 'acquire': ''
+ 'trustee': ''
+ 'mortgage': ''
+ 'transfer': ''
+ 'debenture': ''
+
+resolution_category:
+ 'capital': ''
+ 'incorporation': ''
+ 'miscellaneous': ''
+ 'resolution': ''
+ 'change-of-name': ''
+ 'liquidation': ''
+ 'auditors': ''
+ 'insolvency': ''
+
diff --git a/enumerations/extra/insolvency.yml b/enumerations/extra/insolvency.yml
new file mode 100644
index 0000000..b5fe75f
--- /dev/null
+++ b/enumerations/extra/insolvency.yml
@@ -0,0 +1,31 @@
+insolvency_status:
+ 'live-propopsed-transfer-from-gb': ''
+ 'voluntary-arrangement': ''
+ 'voluntary-arrangement-receivership': ''
+ 'administration-order': ''
+ 'live-receiver-manager-on-at-least-one-charge': ''
+ 'administrative-receiver': ''
+ 'receiver-manager-or-administrative-receiver': ''
+ 'receiver-manager': ''
+ 'receivership': ''
+ 'in-administration': ''
+ 'liquidation': ''
+
+insolvency_case_date_type:
+ 'instrumented-on': ''
+ 'administration-started-on': ''
+ 'administration-discharged-on': ''
+ 'administration-ended-on': ''
+ 'concluded-winding-up-on': ''
+ 'petitioned-on': ''
+ 'ordered-to-wind-up-on': ''
+ 'due-to-be-dissolved-on': ''
+ 'case-end-on': ''
+ 'wound-up-on': ''
+ 'voluntary-arrangement-started-on': ''
+ 'voluntary-arrangement-ended-on': ''
+ 'moratorium-started-on': ''
+ 'moratorium-ended-on': ''
+ 'declaration-solvent-on': ''
+ 'dissolved-on': ''
+
diff --git a/enumerations/extra/insolvency_case_type.yml b/enumerations/extra/insolvency_case_type.yml
new file mode 100644
index 0000000..d74c7bf
--- /dev/null
+++ b/enumerations/extra/insolvency_case_type.yml
@@ -0,0 +1,23 @@
+insolvency_case_type:
+ 'compulsory-liquidation' : "Compulsory liquidation"
+ 'in-administration' : "In administration"
+ 'creditors-voluntary-liquidation' : "Creditors voluntary liquidation"
+ 'members-voluntary-liquidation' : "Members voluntary liquidation"
+ 'foreign-insolvency' : "Foreign insolvency"
+ 'administrative-receiver' : "Administrative receiver appointed"
+ 'scottish-administrative-receiver' : "Receiver (Scotland) appointed"
+ 'administration-order' : "Administration order"
+ 'corporate-voluntary-arrangement' : "Corporate voluntary arrangement (CVA)"
+ 'receiver-manager' : "Receiver/Manager appointed"
+ 'corporate-voluntary-arrangement-moratorium' : "Corporate voluntary arrangement moratorium"
+ 'order-of-court-restructuring-plan' : "Restructuring plan"
+ 'liquidation-moratorium-commencement-of-moratorium' : "Commencement of Moratorium"
+ 'liquidation-moratorium-extension-of-moratorium' : "Moratorium has been ended or extended"
+ 'liquidation-moratorium-early-end-of-moratorium' : "Early end of Moratorium"
+ 'liquidation-moratorium-end-of-moratorium-by-monitor' : "End of Moratorium by a Monitor"
+ 'liquidation-moratorium-end-of-moratorium-by-court' : "End of Moratorium by a Court"
+ 'liquidation-moratorium-end-of-moratorium-following-disposal-of-application-for-extension-by-court-or-following-cva-proposal-taking-effect-or-being-withdrawn' : "End of Moratorium following disposal of application for extension by the court or following CVA proposal taking effect or being withdrawn"
+ 'liquidation-moratorium-court-order-permitting-disposal-of-goods' : "Court order permitting disposal of property or goods"
+ 'liquidation-moratorium-replacement-or-additonal-monitor-following-court-order' : "Replacement or additional monitor (following court order)"
+ 'liquidation-moratorium-monitor-ceasing-to-act-following-court-order' : "Monitor ceasing to act following court"
+ 'moratorium' : "Moratorium"
diff --git a/enumerations/extra/psc.yml b/enumerations/extra/psc.yml
new file mode 100644
index 0000000..a380762
--- /dev/null
+++ b/enumerations/extra/psc.yml
@@ -0,0 +1,84 @@
+person_with_significant_control_kind:
+ 'corporate-entity-person-with-significant-control': ''
+ 'corporate-entity-beneficial-owner': ''
+ 'individual-person-with-significant-control': ''
+ 'individual-beneficial-owner': ''
+ 'super-secure-person-with-significant-control': ''
+ 'super-secure-beneficial-owner': ''
+ 'legal-person-person-with-significant-control': ''
+ 'legal-person-beneficial-owner': ''
+
+person_with_significant_control_nature_of_control:
+ 'ownership-of-shares-25-to-50-percent': ''
+ 'ownership-of-shares-50-to-75-percent': ''
+ 'ownership-of-shares-75-to-100-percent': ''
+ 'ownership-of-shares-25-to-50-percent-as-trust': ''
+ 'ownership-of-shares-50-to-75-percent-as-trust': ''
+ 'ownership-of-shares-75-to-100-percent-as-trust': ''
+ 'ownership-of-shares-25-to-50-percent-as-firm': ''
+ 'ownership-of-shares-50-to-75-percent-as-firm': ''
+ 'ownership-of-shares-75-to-100-percent-as-firm': ''
+ 'ownership-of-shares-more-than-25-percent-registered-overseas-entity': ''
+ 'ownership-of-shares-more-than-25-percent-as-trust-registered-overseas-entity': ''
+ 'ownership-of-shares-more-than-25-percent-as-firm-registered-overseas-entity': ''
+ 'voting-rights-25-to-50-percent': ''
+ 'voting-rights-50-to-75-percent': ''
+ 'voting-rights-75-to-100-percent': ''
+ 'voting-rights-25-to-50-percent-as-trust': ''
+ 'voting-rights-50-to-75-percent-as-trust': ''
+ 'voting-rights-75-to-100-percent-as-trust': ''
+ 'voting-rights-25-to-50-percent-as-firm': ''
+ 'voting-rights-50-to-75-percent-as-firm': ''
+ 'voting-rights-75-to-100-percent-as-firm': ''
+ 'voting-rights-more-than-25-percent-registered-overseas-entity': ''
+ 'voting-rights-more-than-25-percent-as-trust-registered-overseas-entity': ''
+ 'voting-rights-more-than-25-percent-as-firm-registered-overseas-entity': ''
+ 'right-to-appoint-and-remove-directors': ''
+ 'right-to-appoint-and-remove-directors-as-trust': ''
+ 'right-to-appoint-and-remove-directors-as-firm': ''
+ 'significant-influence-or-control': ''
+ 'significant-influence-or-control-as-trust': ''
+ 'significant-influence-or-control-as-firm': ''
+ 'right-to-share-surplus-assets-25-to-50-percent-limited-liability-partnership': ''
+ 'right-to-share-surplus-assets-50-to-75-percent-limited-liability-partnership': ''
+ 'right-to-share-surplus-assets-75-to-100-percent-limited-liability-partnership': ''
+ 'right-to-share-surplus-assets-25-to-50-percent-as-trust-limited-liability-partnership': ''
+ 'right-to-share-surplus-assets-50-to-75-percent-as-trust-limited-liability-partnership': ''
+ 'right-to-share-surplus-assets-75-to-100-percent-as-trust-limited-liability-partnership': ''
+ 'right-to-share-surplus-assets-25-to-50-percent-as-firm-limited-liability-partnership': ''
+ 'right-to-share-surplus-assets-50-to-75-percent-as-firm-limited-liability-partnership': ''
+ 'right-to-share-surplus-assets-75-to-100-percent-as-firm-limited-liability-partnership': ''
+ 'voting-rights-25-to-50-percent-limited-liability-partnership': ''
+ 'voting-rights-50-to-75-percent-limited-liability-partnership': ''
+ 'voting-rights-75-to-100-percent-limited-liability-partnership': ''
+ 'voting-rights-25-to-50-percent-as-trust-limited-liability-partnership': ''
+ 'voting-rights-50-to-75-percent-as-trust-limited-liability-partnership': ''
+ 'voting-rights-75-to-100-percent-as-trust-limited-liability-partnership': ''
+ 'voting-rights-25-to-50-percent-as-firm-limited-liability-partnership': ''
+ 'voting-rights-50-to-75-percent-as-firm-limited-liability-partnership': ''
+ 'voting-rights-75-to-100-percent-as-firm-limited-liability-partnership': ''
+ 'right-to-appoint-and-remove-members-limited-liability-partnership': ''
+ 'right-to-appoint-and-remove-members-as-trust-limited-liability-partnership': ''
+ 'right-to-appoint-and-remove-members-as-firm-limited-liability-partnership': ''
+ 'significant-influence-or-control-limited-liability-partnership': ''
+ 'significant-influence-or-control-as-trust-limited-liability-partnership': ''
+ 'significant-influence-or-control-as-firm-limited-liability-partnership': ''
+ 'significant-influence-or-control-registered-overseas-entity': ''
+ 'significant-influence-or-control-as-trust-registered-overseas-entity': ''
+ 'significant-influence-or-control-as-firm-registered-overseas-entity': ''
+ 'part-right-to-share-surplus-assets-25-to-50-percent': ''
+ 'part-right-to-share-surplus-assets-50-to-75-percent': ''
+ 'part-right-to-share-surplus-assets-75-to-100-percent': ''
+ 'part-right-to-share-surplus-assets-25-to-50-percent-as-trust': ''
+ 'part-right-to-share-surplus-assets-50-to-75-percent-as-trust': ''
+ 'part-right-to-share-surplus-assets-75-to-100-percent-as-trust': ''
+ 'part-right-to-share-surplus-assets-25-to-50-percent-as-firm': ''
+ 'part-right-to-share-surplus-assets-50-to-75-percent-as-firm': ''
+ 'part-right-to-share-surplus-assets-75-to-100-percent-as-firm': ''
+ 'right-to-appoint-and-remove-person': ''
+ 'right-to-appoint-and-remove-person-as-firm': ''
+ 'right-to-appoint-and-remove-person-as-trust': ''
+ 'right-to-appoint-and-remove-directors-registered-overseas-entity': ''
+ 'right-to-appoint-and-remove-directors-as-trust-registered-overseas-entity': ''
+ 'right-to-appoint-and-remove-directors-as-firm-registered-overseas-entity': ''
+
diff --git a/external/api-enumerations b/external/api-enumerations
new file mode 160000
index 0000000..9d9af10
--- /dev/null
+++ b/external/api-enumerations
@@ -0,0 +1 @@
+Subproject commit 9d9af10e2504bfdd08bd69228e25f18c76dc760f
diff --git a/global.json b/global.json
new file mode 100644
index 0000000..d46d21e
--- /dev/null
+++ b/global.json
@@ -0,0 +1,7 @@
+{
+ "sdk": {
+ "version": "10.0.100",
+ "rollForward": "latestFeature",
+ "allowPrerelease": false
+ }
+}
diff --git a/samples/SampleProject/Program.cs b/samples/SampleProject/Program.cs
index b2c56e7..43aa2ab 100644
--- a/samples/SampleProject/Program.cs
+++ b/samples/SampleProject/Program.cs
@@ -1,71 +1,155 @@
-using CompaniesHouse;
+using CompaniesHouse;
using CompaniesHouse.Request;
-using CompaniesHouse.Response.Search.OfficerSearch;
+using CompaniesHouse.Response;
+using CompaniesHouse.Response.CompanyProfile;
+using CompaniesHouse.Response.Search.AllSearch;
using CompaniesHouse.Response.Search.CompanySearch;
using CompaniesHouse.Response.Search.DisqualifiedOfficersSearch;
-using System;
-using System.Linq;
-using System.Threading.Tasks;
-using CompaniesHouse.Response.Search.AllSearch;
+using CompaniesHouse.Response.Search.OfficerSearch;
+using Microsoft.Extensions.DependencyInjection;
+using Officers = CompaniesHouse.Response.Officers.Officers;
+
+namespace SampleProject;
-namespace SampleProject
+class Program
{
- class Program
+ // Add your API key from https://developer.company-information.service.gov.uk/
+ private const string ApiKey = "";
+
+ static async Task Main()
{
- static async Task Main( string[] args )
+ if (string.IsNullOrEmpty(ApiKey))
{
- string api_key = ""; //Add your api key from companies house api here https://developer.companieshouse.gov.uk/developer/applications
- if (!api_key.Any())
- {
- Console.WriteLine( $"No API Key found. Please edit Program.cs to add it in." );
- return;
- }
-
- Console.WriteLine( $"Starting up - Found this api key: {api_key}" );
- CompaniesHouseClientResponse result = null;
- string nameToSearchFor = "Bigman";
- var settings = new CompaniesHouseSettings( api_key );
- using (var client = new CompaniesHouseClient( settings ))
- {
- var request = new SearchAllRequest()
- {
- Query = nameToSearchFor,
- StartIndex = 0,
- ItemsPerPage = 10
- };
-
- result = await client.SearchAllAsync( request );
- }
-
- DisplayResults( result, nameToSearchFor );
+ Console.WriteLine("No API Key found. Please edit Program.cs to add it in.");
+ return;
}
- private static void DisplayResults( CompaniesHouseClientResponse result, string nameSearchedFor )
+ const string companyNumber = "10440441";
+ const string nameToSearchFor = "Bigman";
+
+ await RunWithDirectClientAsync(nameToSearchFor, companyNumber);
+ await RunWithDependencyInjectionAsync(companyNumber);
+ }
+
+ ///
+ /// The simplest way to use the client: construct and
+ /// directly. Prefer the DI path below in ASP.NET Core apps.
+ ///
+ private static async Task RunWithDirectClientAsync(string nameToSearchFor, string companyNumber)
+ {
+ var settings = new CompaniesHouseSettings(ApiKey);
+ using var client = new CompaniesHouseClient(settings);
+
+ var searchResult = await client.SearchAllAsync(new SearchAllRequest
{
+ Query = nameToSearchFor,
+ StartIndex = 0,
+ ItemsPerPage = 10
+ });
+
+ DisplaySearchResults(searchResult, nameToSearchFor);
- //Show all companies found
- Console.WriteLine( $"{Environment.NewLine}----------------------------------------------" );
- Console.WriteLine( $"Companies found when searching for '{nameSearchedFor}' :" );
- foreach (Company item in result.Data.Items.Where( t => t as Company != null ))
- {
- Console.WriteLine( $"* {item.Title} - {item.Description} - {item.CompanyStatus}" );
- }
-
- //Show all Officers found
- Console.WriteLine( $"{Environment.NewLine}----------------------------------------------" );
- Console.WriteLine( $"Officers found when searching for '{nameSearchedFor}' :" );
- foreach (Officer item in result.Data.Items.Where( t => t as Officer != null ))
- {
- Console.WriteLine( $"* {item.Title} - {item.Description}" );
- }
-
- //Show all Disqualified Officers found
- Console.WriteLine( $"{Environment.NewLine}----------------------------------------------" );
- Console.WriteLine( $"Disqualified Officers found when searching for '{nameSearchedFor}' :" );
- foreach (DisqualifiedOfficer item in result.Data.Items.Where( t => t as DisqualifiedOfficer != null ))
- {
- Console.WriteLine( $"* {item.Title}" );
- }
+ var officersResult = await client.GetOfficersAsync(companyNumber);
+ DisplayOfficers(officersResult, companyNumber);
+ }
+
+ ///
+ /// The recommended way to use the client from an app with an
+ /// (ASP.NET Core, worker services, etc.).
+ ///
+ private static async Task RunWithDependencyInjectionAsync(string companyNumber)
+ {
+ var services = new ServiceCollection();
+ services.AddCompaniesHouseClient(ApiKey);
+ await using var provider = services.BuildServiceProvider();
+
+ var client = provider.GetRequiredService();
+
+ var result = await client.GetCompanyProfileAsync(companyNumber);
+ DisplayCompanyProfile(result, companyNumber);
+ }
+
+ private static void DisplaySearchResults(CompaniesHouseResponse result, string query)
+ {
+ Console.WriteLine($"\n----------------------------------------------");
+
+ // .Data throws InvalidOperationException on non-success — pattern-match
+ // when you need to handle error outcomes explicitly.
+ if (result is not CompaniesHouseResponse.Success { Data: var data })
+ {
+ Console.WriteLine($"Search failed (HTTP {result.StatusCode}).");
+ return;
}
+
+ Console.WriteLine($"Companies matching '{query}':");
+ foreach (var item in (data.Items ?? []).OfType())
+ {
+ // CompanyStatus is a string-backed value type — it never throws on an
+ // unrecognised wire value, so we can always describe it safely.
+ Console.WriteLine($" * {item.Title} — {DescribeCompanyStatus(item.CompanyStatus)}");
+ }
+
+ Console.WriteLine($"\nOfficers matching '{query}':");
+ foreach (var item in (data.Items ?? []).OfType())
+ Console.WriteLine($" * {item.Title} — {item.Description}");
+
+ Console.WriteLine($"\nDisqualified officers matching '{query}':");
+ foreach (var item in (data.Items ?? []).OfType())
+ Console.WriteLine($" * {item.Title}");
+ }
+
+ private static void DisplayOfficers(CompaniesHouseResponse result, string companyNumber)
+ {
+ Console.WriteLine($"\n----------------------------------------------");
+ Console.WriteLine($"Officers for {companyNumber}:");
+
+ if (result is not CompaniesHouseResponse.Success { Data: var data })
+ {
+ Console.WriteLine($" Could not retrieve officers (HTTP {result.StatusCode}).");
+ return;
+ }
+
+ foreach (var officer in data.Items ?? [])
+ Console.WriteLine($" * {officer.Name}");
}
+
+ private static void DisplayCompanyProfile(CompaniesHouseResponse result, string companyNumber)
+ {
+ Console.WriteLine($"\n----------------------------------------------");
+
+ // Switch expression — the compiler guides you through every outcome.
+ var summary = result switch
+ {
+ CompaniesHouseResponse.Success { Data: var company } =>
+ $"{company.CompanyName} — {DescribeCompanyStatus(company.CompanyStatus)}",
+
+ CompaniesHouseResponse.NotFound =>
+ $"Company {companyNumber} not found.",
+
+ CompaniesHouseResponse.RateLimited { RetryAfter: var delay } =>
+ $"Rate limited — retry after {delay}.",
+
+ CompaniesHouseResponse.Unauthorized =>
+ "Unauthorized — check your API key.",
+
+ CompaniesHouseResponse.ServerError { StatusCode: var code } =>
+ $"Server error {code} — try again later.",
+
+ _ => $"Unexpected response: {result.StatusCode}",
+ };
+
+ Console.WriteLine(summary);
+ }
+
+ ///
+ /// String-backed value types never throw for an unrecognised value, so this
+ /// switch can handle future Companies House statuses gracefully.
+ ///
+ private static string DescribeCompanyStatus(CompanyStatus status) => status switch
+ {
+ _ when status == CompanyStatus.Active => "active",
+ _ when status == CompanyStatus.Dissolved => "dissolved",
+ _ when status.IsKnown => status.Description ?? status.Value,
+ _ => $"unknown status ({status.Value})",
+ };
}
diff --git a/samples/SampleProject/SampleProject.csproj b/samples/SampleProject/SampleProject.csproj
index f5aafc9..bc3e02d 100644
--- a/samples/SampleProject/SampleProject.csproj
+++ b/samples/SampleProject/SampleProject.csproj
@@ -1,12 +1,17 @@
Exe
- net7.0
+ net10.0
false
+
+
+
+
+
diff --git a/spec/swagger.json b/spec/swagger.json
new file mode 100644
index 0000000..9728af3
--- /dev/null
+++ b/spec/swagger.json
@@ -0,0 +1,197 @@
+{
+ "swagger": "2.0",
+ "info": {
+ "version": "1.0.0",
+ "title": "Companies House Public Data API",
+ "description": "An API suite providing read only access to search and retrieve public company data"
+ },
+ "host": "api.company-information.service.gov.uk",
+ "schemes": [
+ "https",
+ "http"
+ ],
+ "consumes": [
+ "application/json"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "tags": [{
+ "name": "registeredOfficeAddress",
+ "description": "Registered office address"
+ },
+ {
+ "name": "companyProfile",
+ "description": "Company profile"
+ },
+ {
+ "name": "search",
+ "description": "Search"
+ },
+ {
+ "name": "officers",
+ "description": "Officers"
+ },
+ {
+ "name": "registers",
+ "description": "Registers"
+ },
+ {
+ "name": "charges",
+ "description": "Charges"
+ },
+ {
+ "name": "filingHistory",
+ "description": "Filing history"
+ },
+ {
+ "name": "insolvency",
+ "description": "Insolvency"
+ },
+ {
+ "name": "exemptions",
+ "description": "Exemptions"
+ },
+ {
+ "name": "officerDisqualifications",
+ "description": "Officer disqualifications"
+ },
+ {
+ "name": "officerAppointments",
+ "description": "Officer appointments"
+ },
+ {
+ "name": "UKEstablishments",
+ "description": "UK Establishments"
+ },
+ {
+ "name": "personsWithSignificantControl",
+ "description": "Persons with significant control"
+ },
+ {
+ "name": "pscDiscrepancies",
+ "description": "PSC discrepancies"
+ },
+ {
+ "name": "personsWithSignificantControlNotifications",
+ "description": "Persons with significant control notifications"
+ }
+ ],
+ "securityDefinitions": {
+ "api_key": {
+ "type": "apiKey",
+ "name": "api_key",
+ "in": "header"
+ }
+ },
+ "security": [{
+ "api_key": []
+ }
+ ],
+ "paths": {
+ "/company/{companyNumber}/registered-office-address": {
+ "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyAddress.json#/getCompanyAddress"
+ },
+ "/company/{companyNumber}": {
+ "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyProfile.json"
+ },
+ "/search": {
+ "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/search.json#/searchAll"
+ },
+ "/search/companies": {
+ "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/search.json#/searchCompanies"
+ },
+ "/search/officers": {
+ "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/search.json#/searchOfficers"
+ },
+ "/search/disqualified-officers": {
+ "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/search.json#/searchDisqualified-officers"
+ },
+ "/dissolved-search/companies": {
+ "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/search-companies.json#/searchDissolved"
+ },
+ "/alphabetical-search/companies": {
+ "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/search-companies.json#/searchAlphabetic"
+ },
+ "/advanced-search/companies": {
+ "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/search-companies.json#/searchAdvanced"
+ },
+ "/company/{company_number}/officers": {
+ "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyOfficerList.json#/listCompanyOfficers"
+ },
+ "/company/{company_number}/appointments/{appointment_id}": {
+ "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyOfficerList.json#/getCompanyOfficerAppointment"
+ },
+ "/company/{company_number}/registers": {
+ "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyRegisters.json"
+ },
+ "/company/{company_number}/filing-history/{transaction_id}": {
+ "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/filingHistory.json#/getFilingHistory"
+ },
+ "/company/{company_number}/filing-history": {
+ "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/filingHistory.json#/listFilingHistory"
+ },
+ "/company/{company_number}/exemptions": {
+ "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/exemptions.json"
+ },
+ "/disqualified-officers/natural/{officer_id}": {
+ "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/disqualifications.json#/getNatural"
+ },
+ "/disqualified-officers/corporate/{officer_id}": {
+ "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/disqualifications.json#/getCorporate"
+ },
+ "/officers/{officer_id}/appointments": {
+ "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/officerAppointmentList.json"
+ },
+ "/company/{company_number}/charges": {
+ "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/charges.json#/chargeList"
+ },
+ "/company/{company_number}/charges/{charge_id}": {
+ "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/charges.json#/chargeDetails"
+ },
+ "/company/{company_number}/insolvency": {
+ "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/insolvency.json#/insolvencyCase"
+ },
+ "/company/{company_number}/uk-establishments": {
+ "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyUKEstablishments.json"
+ },
+ "/company/{company_number}/persons-with-significant-control": {
+ "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/listCompanyPSC"
+ },
+ "/company/{company_number}/persons-with-significant-control/individual/{notification_id}": {
+ "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/getIndividualPSC"
+ },
+ "/company/{company_number}/persons-with-significant-control/individual-beneficial-owner/{notification_id}": {
+ "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/getIndividualBO"
+ },
+ "/company/{company_number}/persons-with-significant-control/corporate-entity/{notification_id}": {
+ "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/getCorporateEntityPSC"
+ },
+ "/company/{company_number}/persons-with-significant-control/corporate-entity-beneficial-owner/{notification_id}": {
+ "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/getCorporateEntityBO"
+ },
+ "/company/{company_number}/persons-with-significant-control/legal-person/{notification_id}": {
+ "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/getLegalPersonPSC"
+ },
+ "/company/{company_number}/persons-with-significant-control/legal-person-beneficial-owner/{notification_id}": {
+ "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/getLegalPersonBO"
+ },
+ "/company/{company_number}/persons-with-significant-control-statements": {
+ "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/listCompanyPSCStatements"
+ },
+ "/company/{company_number}/persons-with-significant-control-statements/{statement_id}": {
+ "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/getPSCStatement"
+ },
+ "/company/{company_number}/persons-with-significant-control/super-secure/{super_secure_id}": {
+ "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/getSuperSecurePSC"
+ },
+ "/company/{company_number}/persons-with-significant-control/super-secure-beneficial-owner/{super_secure_id}": {
+ "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/getSuperSecureBO"
+ },
+ "/company/{company_number}/persons-with-significant-control/{psc_id}/notifications": {
+ "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/pscNotificationList.json"
+ }
+ }
+}
+
+
diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/errors.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/errors.json
new file mode 100644
index 0000000..91e35fb
--- /dev/null
+++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/errors.json
@@ -0,0 +1,107 @@
+{
+ "definitions": {
+ "apiError": {
+ "title": "apiError",
+ "type": "object",
+ "required": [
+ "type",
+ "error"
+ ],
+ "properties": {
+ "type": {
+ "type": "string",
+ "description": "Type of error.",
+ "enum": [
+ "ch:validation",
+ "ch:service"
+ ],
+ "readOnly": true
+ },
+ "error": {
+ "type": "string",
+ "description": "The enumerated error being returned. See github for valid error enumeration types.",
+ "readOnly": true
+ },
+ "location_type": {
+ "type": "string",
+ "description": "Describes the type of location returned so that it may be parsed appropriately.",
+ "enum": [
+ "json-path",
+ "query-parameter"
+ ],
+ "readOnly": true
+ },
+ "location": {
+ "type": "string",
+ "description": "The location in the submitted request in which the error relates. This parameter is only provided when errors[].type is set to \"ch:validation\".",
+ "readOnly": true
+ },
+ "error_values": {
+ "type": "object",
+ "description": "A collection of argument name and value pairs which, when substituted into the error string, provide the full description of the error. As many name/value pairs as necessary to complete the error description are returned. See example above.",
+ "additionalProperties": {
+ "type": "string",
+ "readOnly": true,
+ "description": "key / value string pair."
+ },
+ "readOnly": true
+ }
+ }
+ },
+ "apiErrors": {
+ "title": "apiErrors",
+ "properties": {
+ "errors": {
+ "type": "array",
+ "description": "List of errors.",
+ "items": {
+ "$ref": "errors.json#/definitions/apiError"
+ },
+ "readOnly": true
+ }
+
+ }
+ },
+ "validationStatus": {
+ "title": "validationStatus",
+ "type": "object",
+ "required": [
+ "is_valid"
+ ],
+ "allOf": [{
+ "$ref": "errors.json#/definitions/apiErrors"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "is_valid": {
+ "type": "boolean",
+ "description": "Indicates whether the resource is valid in its current state. If `false` the resource is invalid and `ch:validation` errors will be included in the `errors` array.",
+ "readOnly": true
+ }
+ }
+ }
+ ]
+
+ },
+ "companyValidation": {
+ "title": "companyValidation",
+ "type": "object",
+ "required": [
+ "eligibility_status_code"
+ ],
+ "allOf": [{
+ "type": "object",
+ "properties": {
+ "eligibility_status_code": {
+ "type": "string",
+ "enum": ["INVALID_NO_REGISTERED_EMAIL_ADDRESS_EXISTS", "INVALID_COMPANY_STATUS", "INVALID_COMPANY_TYPE", "COMPANY_NOT_FOUND", "COMPANY_VALID_FOR_SERVICE"],
+ "readOnly": true
+ }
+ }
+ }
+ ]
+
+ }
+ }
+}
diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/filings.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/filings.json
new file mode 100644
index 0000000..8672ae9
--- /dev/null
+++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/filings.json
@@ -0,0 +1,48 @@
+{
+ "definitions": {
+ "filing": {
+ "title": "Filing",
+ "description": "Filing resource",
+ "required": [
+ "company_number",
+ "description_identifier",
+ "description",
+ "kind",
+ "description_values",
+ "data"
+ ],
+ "properties": {
+ "company_number": {
+ "type": "string",
+ "description": "The company registration / incorporation number of the company."
+ },
+ "description_identifier": {
+ "type": "string",
+ "description": "An array of enumeration types that make up the description."
+ },
+ "description": {
+ "type": "string",
+ "description": "Description of the filing."
+ },
+ "kind": {
+ "type": "string",
+ "description": "Type of filing."
+ },
+ "description_values": {
+ "description": "Data required for the filing description.",
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
+ },
+ "data": {
+ "description": "Data for the filing.",
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/genericModels.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/genericModels.json
new file mode 100644
index 0000000..ccd3eeb
--- /dev/null
+++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/genericModels.json
@@ -0,0 +1,17 @@
+{
+ "definitions": {
+ "selfLink": {
+ "required": [
+ "self"
+ ],
+ "properties": {
+ "self": {
+ "description": "URL to this resource.",
+ "readOnly": true,
+ "type": "string",
+ "format": "uri"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/insolvency.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/insolvency.json
new file mode 100644
index 0000000..0147d0c
--- /dev/null
+++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/insolvency.json
@@ -0,0 +1,539 @@
+{
+ "definitions": {
+ "insolvencyResourceWritable": {
+ "title": "writeable insolvency",
+ "type": "object",
+ "required": [
+ "company_number",
+ "company_name",
+ "case_type"
+ ],
+ "properties": {
+ "company_number": {
+ "type": "string"
+ },
+ "company_name": {
+ "type": "string"
+ },
+ "case_type": {
+ "type": "string",
+ "enum": [
+ "creditors-voluntary-liquidation"
+ ]
+ }
+ }
+ },
+
+ "createdInsolvencyResource": {
+ "title": "CreatedInsolvency",
+ "type": "object",
+ "properties": {
+ "company_number": {
+ "type": "string"
+ },
+ "case_type": {
+ "type": "string",
+ "enum": [
+ "creditors-voluntary-liquidation"
+ ]
+ },
+ "etag": {
+ "type": "string"
+ },
+ "kind": {
+ "type": "string",
+ "enum": [
+ "insolvency-resource#insolvency-resource"
+ ]
+ },
+ "company_name": {
+ "type": "string"
+ },
+ "links": {
+ "type": "object",
+ "properties": {
+ "self": {
+ "type": "string",
+ "format": "uri",
+ "example": "/transactions/{transaction_id}/insolvency"
+ },
+ "transaction": {
+ "type": "string",
+ "format": "uri",
+ "example": "/transactions/{transaction_id}"
+ },
+ "validation_status": {
+ "type": "string",
+ "format": "uri",
+ "example": "/transactions/{transaction_id}/insolvency/validation-status"
+ }
+ }
+ }
+ }
+ },
+ "practitionerWritable": {
+ "title": "writeable practitioner",
+ "type": "object",
+ "required": [
+ "first_name",
+ "last_name",
+ "ip_code",
+ "email",
+ "telephone_number",
+ "address"
+ ],
+ "properties": {
+ "first_name": {
+ "type": "string"
+ },
+ "last_name": {
+ "type": "string"
+ },
+ "ip_code": {
+ "type": "string"
+ },
+ "address": {
+ "type": "object",
+ "$ref": "insolvency.json#/definitions/address"
+ },
+ "role": {
+ "type": "string",
+ "enum": [
+ "final-liquidator",
+ "receiver",
+ "receiver-manager",
+ "proposed-liquidator",
+ "provisional-liquidator",
+ "administrative-receiver",
+ "practitioner",
+ "interim-liquidator"
+ ]
+ },
+ "email": {
+ "type": "string",
+ "format": "email",
+ "description": "At least one of email or telephone_number must be supplied."
+ },
+ "telephone_number": {
+ "type": "string",
+ "description": "At least one of email or telephone_number must be supplied."
+ }
+ }
+ },
+ "practitioner": {
+ "title": "practitioner",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "#/definitions/practitionerWritable"
+ },
+ {"properties": {
+ "appointed_on": {
+ "type": "string",
+ "format": "date"
+ },
+ "etag": {
+ "type": "string"
+ },
+ "kind": {
+ "type": "string",
+ "enum": [
+ "insolvency-resource#liquidator"
+ ]
+ },
+ "links": {
+ "type": "object",
+ "$ref": "genericModels.json#/definitions/selfLink"
+ }
+ }
+ }
+
+ ]
+ },
+ "allPractitioners": {
+ "title": "allPractitioners",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/practitioner"
+ }
+ },
+
+ "address": {
+ "title": "address",
+ "type": "object",
+ "required": [
+ "premises",
+ "address_line_1",
+ "locality",
+ "postal_code"
+ ],
+ "properties": {
+ "premises": {
+ "type": "string"
+ },
+ "address_line_1": {
+ "type": "string"
+ },
+ "address_line_2": {
+ "type": "string"
+ },
+ "country": {
+ "type": "string"
+ },
+ "locality": {
+ "type": "string"
+ },
+ "region": {
+ "type": "string"
+ },
+ "postal_code": {
+ "type": "string"
+ },
+ "po_box": {
+ "type": "string"
+ }
+ }
+ },
+ "appointment": {
+ "title": "appointment",
+ "type": "object",
+ "required": [
+ "appointed_on",
+ "made_by"
+ ],
+ "properties": {
+ "appointed_on": {
+ "type": "string",
+ "format": "date"
+ },
+ "made_by": {
+ "type": "string",
+ "enum": [
+ "creditors"
+ ]
+ }
+ }
+ },
+ "practitionerAppointment": {
+ "title": "practitionerAppointment",
+ "type": "object",
+ "properties": {
+ "appointed_on": {
+ "type": "string",
+ "format": "date"
+ },
+ "made_by": {
+ "type": "string",
+ "enum": [
+ "creditors"
+ ]
+ },
+ "links": {
+ "type": "object",
+ "$ref": "genericModels.json#/definitions/selfLink"
+ }
+ }
+ },
+ "createdAttachment": {
+ "title": "created attachment",
+ "type": "object",
+ "properties": {
+ "attachment_type": {
+ "type": "string",
+ "enum": [
+ "resolution",
+ "statement-of-affairs-director",
+ "statement-of-concurrence",
+ "progress-report"
+ ]
+ },
+ "file": {
+ "type": "object",
+ "description": "The file name, size and content type",
+ "items": {
+ "$ref": "insolvency.json#/definitions/file"
+ }
+ },
+ "etag": {
+ "type": "string"
+ },
+ "kind": {
+ "type": "string",
+ "enum": [
+ "insolvency-resources#attachment"
+ ]
+ },
+ "status": {
+ "type": "string"
+ },
+ "links": {
+ "type": "object",
+ "properties": {
+ "self": {
+ "type": "string",
+ "format": "uri",
+ "description": "URL to this resource",
+ "example": "/transactions/010276-506416-629750/insolvency/attachments/b303f91d-bc28-469a-b325-6c9030eec26f"
+ },
+ "download": {
+ "type": "string",
+ "format": "uri",
+ "description": "URL to download the file",
+ "example": "/transactions/010276-506416-629750/insolvency/attachments/b303f91d-bc28-469a-b325-6c9030eec26f/download"
+ }
+ }
+ }
+ }
+ },
+ "attachmentWriteable": {
+ "title": "writeable attachment",
+ "type": "object",
+ "required": [
+ "attachment_type",
+ "file"
+ ],
+ "content": "multipart/formdata",
+ "properties": {
+ "attachment_type": {
+ "type": "string",
+ "enum": [
+ "resolution",
+ "statement-of-affairs-director",
+ "statement-of-concurrence",
+ "progress-report"
+ ]
+ },
+ "file": {
+ "type": "array",
+ "items":{
+ "type": "string",
+ "format": "binary",
+ "description": "Files attached in request can be a maximum of 4MB in size"
+ }
+ }
+ }
+ },
+ "file": {
+ "title": "file",
+ "required": [
+ "name",
+ "size",
+ "content_type"
+ ],
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "The file name"
+ },
+ "size": {
+ "type": "string",
+ "description": "The size of the file"
+ },
+ "content_type": {
+ "type": "string",
+ "description": "The media type being consumed",
+ "enum": [
+ "application/pdf"
+ ]
+ }
+ }
+ },
+ "downloadedAttachment": {
+ "title": "attachment download",
+ "required": [
+ "content_type"
+ ],
+ "properties": {
+ "content_type": {
+ "type": "string",
+ "format": "binary",
+ "description": "The media type being consumed",
+ "enum": [
+ "application/pdf"
+ ]
+ }
+ }
+ },
+ "resolutionResourceWriteable": {
+ "title": "writeable resolution",
+ "required": [
+ "date_of_resolution",
+ "attachments"
+ ],
+ "properties": {
+ "date_of_resolution": {
+ "type": "string",
+ "format": "date"
+ },
+ "attachments": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "format": "uuid"
+ }
+ }
+ }
+ },
+ "Resolution": {
+ "title": "Resolution",
+ "required": [
+ "date_of_resolution",
+ "attachments",
+ "etag",
+ "kind",
+ "links"
+ ],
+ "type":"object",
+ "properties": {
+ "date_of_resolution": {
+ "type": "string",
+ "format":"date"
+ },
+ "attachments": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "format": "uuid"
+ }
+ },
+ "etag": {
+ "type": "string"
+ },
+ "kind": {
+ "type": "string",
+ "enum": [
+ "insolvency-resource#resolution"
+ ]
+ },
+ "links": {
+ "type": "object",
+ "$ref": "genericModels.json#/definitions/selfLink"
+ }
+ }
+ },
+ "statementOfAffairsWriteable": {
+ "title": "writeable statement of affairs",
+ "required": [
+ "statement_date",
+ "attachments"
+ ],
+ "properties": {
+ "statement_date": {
+ "type": "string",
+ "format": "date"
+ },
+ "attachments": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "format": "uuid"
+ }
+ }
+ }
+ },
+ "statementOfAffairs": {
+ "title": "Statement Of Affairs",
+ "required": [
+ "statement_date",
+ "attachments",
+ "etag",
+ "kind",
+ "links"
+ ],
+ "type":"object",
+ "properties": {
+ "statement_date": {
+ "type": "string",
+ "format":"date"
+ },
+ "attachments": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "format": "uuid"
+ }
+ },
+ "etag": {
+ "type": "string"
+ },
+ "kind": {
+ "type": "string",
+ "enum": [
+ "insolvency-resource#statement-of-affairs"
+ ]
+ },
+ "links": {
+ "type": "object",
+ "$ref": "genericModels.json#/definitions/selfLink"
+ }
+ }
+ },
+ "progressReportWriteable": {
+ "title": "writeable progress report",
+ "required": [
+ "from_date",
+ "to_date",
+ "attachments"
+ ],
+ "properties": {
+ "from_date": {
+ "type": "string",
+ "format": "date"
+ },
+ "to_date": {
+ "type": "string",
+ "format": "date"
+ },
+ "attachments": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "format": "uuid"
+ }
+ }
+ }
+ },
+ "progressReport": {
+ "title": "Progress Report",
+ "required": [
+ "from_date",
+ "to_date",
+ "attachments",
+ "etag",
+ "kind",
+ "links"
+ ],
+ "type":"object",
+ "properties": {
+ "from_date": {
+ "type": "string",
+ "format":"date"
+ },
+ "to_date": {
+ "type": "string",
+ "format":"date"
+ },
+ "attachments": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "format": "uuid"
+ }
+ },
+ "etag": {
+ "type": "string"
+ },
+ "kind": {
+ "type": "string",
+ "enum": [
+ "insolvency-resource#progress-report"
+ ]
+ },
+ "links": {
+ "type": "object",
+ "$ref": "genericModels.json#/definitions/selfLink"
+ }
+ }
+ }
+ }
+}
diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/officerChanges.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/officerChanges.json
new file mode 100644
index 0000000..253a431
--- /dev/null
+++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/officerChanges.json
@@ -0,0 +1,306 @@
+{
+ "definitions": {
+ "officerChange": {
+ "title": "officerChange",
+ "required": [
+ "etag",
+ "kind",
+ "links"
+ ],
+ "properties": {
+ "etag": {
+ "type": "string",
+ "description": "The ETag of the resource.",
+ "readOnly": true
+ },
+ "kind": {
+ "type": "string",
+ "description": "The type of resource.",
+ "enum": [
+ "officer-change#officer-change"
+ ],
+ "readOnly": true
+ },
+ "reference_appointment_id": {
+ "type": "string",
+ "description": "Required for officer change and termination. The id of the current company officer appointment resource being changed or terminated (`/company/{company_number}/appointments/{officer_appointment_id}`) on the public register."
+ },
+ "reference_etag": {
+ "type": "string",
+ "description": "The latest etag read from the current company officer appointment resource (`/company/{company_number}/officer/{officer_id}`) on the public register. If this reference etag does not match the current register the request will be rejected."
+ },
+ "address": {
+ "description": "The correspondence address of the officer. Required for officer appointment.",
+ "items": {
+ "$ref": "officerChanges.json#/definitions/address"
+ },
+ "type": "object"
+ },
+ "appointed_on": {
+ "description": "The date on which the officer was appointed. Required for officer appointment.",
+ "type": "string",
+ "format": "date"
+ },
+ "country_of_residence": {
+ "description": "The officer's country of residence. Required for officer appointment.",
+ "type": "string"
+ },
+ "date_of_birth": {
+ "description": "Details of director date of birth. Required for officer appointment.",
+ "items": {
+ "$ref": "officerChanges.json#/definitions/dateOfBirth"
+ },
+ "type": "object"
+ },
+ "links": {
+ "description": "Links to other resources associated with this officer change resource.",
+ "items": {
+ "$ref": "officerChanges.json#/definitions/itemLinkTypes"
+ },
+ "type": "object"
+ },
+ "name": {
+ "description": "Corporate or natural officer name. Required for officer appointment.",
+ "type": "string"
+ },
+ "nationality": {
+ "description": "The officer's nationality. Required for officer appointment.",
+ "type": "string"
+ },
+ "occupation": {
+ "description": "The officer's job title. Required for officer appointment.",
+ "type": "string"
+ },
+ "officer_role": {
+ "description": "The officer's role. Required for officer appointment.",
+ "enum": [
+ "cic-manager",
+ "corporate-director",
+ "corporate-llp-designated-member",
+ "corporate-llp-member",
+ "corporate-manager-of-an-eeig",
+ "corporate-member-of-a-management-organ",
+ "corporate-member-of-a-supervisory-organ",
+ "corporate-member-of-an-administrative-organ",
+ "corporate-nominee-director",
+ "corporate-nominee-secretary",
+ "corporate-secretary",
+ "director",
+ "general-partner-in-a-limited-partnership",
+ "judicial-factor",
+ "limited-partner-in-a-limited-partnership",
+ "llp-designated-member",
+ "llp-member",
+ "manager-of-an-eeig",
+ "member-of-a-management-organ",
+ "member-of-a-supervisory-organ",
+ "member-of-an-administrative-organ",
+ "nominee-director",
+ "nominee-secretary",
+ "person-authorised-to-accept",
+ "person-authorised-to-represent",
+ "person-authorised-to-represent-and-accept",
+ "receiver-and-manager",
+ "secretary"
+ ],
+ "type": "string"
+ },
+ "resigned_on": {
+ "description": "The date on which the officer resigned.",
+ "type": "string",
+ "format": "date"
+ },
+ "former_names": {
+ "description": "Former names for the officer.",
+ "items": {
+ "$ref": "officerChanges.json#/definitions/formerNames"
+ },
+ "type": "array"
+ },
+ "identification": {
+ "description": "Only one from `eea`, `non-eea`, `uk-limited` or `other-corporate-body-or-firm` can be supplied, not multiples of them. Required for officer appointment.",
+ "items": {
+ "$ref": "officerChanges.json#/definitions/corporateIdent"
+ },
+ "type": "object"
+ },
+ "identity_verification_details": {
+ "description": "Information relating to the identity verification of the officer",
+ "items": {
+ "$ref": "officerChanges.json#/definitions/identityVerificationDetails"
+ },
+ "type": "object"
+ }
+ }
+ },
+ "address": {
+ "title": "address",
+ "required": [
+ "address_line_1",
+ "locality"
+ ],
+ "properties": {
+ "address_line_1": {
+ "description": "The first line of the address.",
+ "type": "string"
+ },
+ "address_line_2": {
+ "description": "The second line of the address.",
+ "type": "string"
+ },
+ "care_of": {
+ "description": "The care of name.",
+ "type": "string"
+ },
+ "country": {
+ "description": "The country e.g. United Kingdom.",
+ "type": "string"
+ },
+ "locality": {
+ "description": "The locality e.g. London.",
+ "type": "string"
+ },
+ "po_box": {
+ "description": "The post-office box number.",
+ "type": "string"
+ },
+ "postal_code": {
+ "description": "The postal code e.g. CF14 3UZ.",
+ "type": "string"
+ },
+ "premises": {
+ "description": "The property name or number.",
+ "type": "string"
+ },
+ "region": {
+ "description": "The region e.g. Surrey.",
+ "type": "string"
+ }
+ }
+ },
+ "itemLinkTypes": {
+ "title": "itemLinkTypes",
+ "required": [
+ "self",
+ "validation_status"
+ ],
+ "properties": {
+ "self": {
+ "description": "Link to this individual company officer appointment resource.",
+ "type": "string",
+ "readOnly": true
+ },
+ "validation_status": {
+ "type": "string",
+ "description": "The URL of the validation status resource for the resource.",
+ "readOnly": true
+ }
+ }
+ },
+ "formerNames": {
+ "title": "formerNames",
+ "properties": {
+ "forenames": {
+ "description": "Former forenames of the officer.",
+ "type": "string"
+ },
+ "surname": {
+ "description": "Former surnames of the officer.",
+ "type": "string"
+ }
+ }
+ },
+ "corporateIdent": {
+ "title": "corporateIdent",
+ "properties": {
+ "identification_type": {
+ "description": "The officer's identity type",
+ "enum": [
+ "eea",
+ "non-eea",
+ "uk-limited",
+ "other-corporate-body-or-firm"
+ ],
+ "type": "string"
+ },
+ "legal_authority": {
+ "description": "The legal authority supervising the company.",
+ "type": "string"
+ },
+ "legal_form": {
+ "description": "The legal form of the company as defined by its country of registration.",
+ "type": "string"
+ },
+ "place_registered": {
+ "description": "Place registered.",
+ "type": "string"
+ },
+ "registration_number": {
+ "description": "Company registration number.",
+ "type": "string"
+ }
+ }
+ },
+ "dateOfBirth": {
+ "title": "dateOfBirth",
+ "properties": {
+ "day": {
+ "description": "The day of the date of birth.",
+ "type": "integer"
+ },
+ "month": {
+ "description": "The month of date of birth.",
+ "type": "integer"
+ },
+ "year": {
+ "description": "The year of date of birth.",
+ "type": "integer"
+ }
+ },
+ "required": [
+ "month",
+ "year"
+ ]
+ },
+ "identityVerificationDetails": {
+ "title": "identityVerificationDetails",
+ "properties": {
+ "anti_money_laundering_supervisory_bodies": {
+ "description": "The Anti-Money Laundering supervisory bodies that the authorised corporate service provider was registered with when verifying the officer.",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "appointment_verification_end_on": {
+ "description": "The date on which the identity verification statement was removed for the appointment.",
+ "type": "string",
+ "format": "date"
+ },
+ "appointment_verification_statement_due_on": {
+ "description": "The date by which an identity verification statement must be supplied for the appointment.",
+ "type": "string",
+ "format": "date"
+ },
+ "appointment_verification_start_on": {
+ "description": "The date on which the identity verification statement was supplied for the appointment.",
+ "type": "string",
+ "format": "date"
+ },
+ "authorised_corporate_service_provider_name": {
+ "description": "The name of the authorised corporate service provider that verified the identity of the officer.",
+ "type": "string"
+ },
+ "identity_verified_on": {
+ "description": "The date on which the authorised corporate service provider verified the identity of the officer.",
+ "type": "string",
+ "format": "date"
+ },
+ "preferred_name": {
+ "description": "The name provided to the authorised corporate service provider by which the officer prefers to be known.",
+ "type": "string"
+ }
+ }
+ }
+ }
+}
diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/registeredOfficeAddress.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/registeredOfficeAddress.json
new file mode 100644
index 0000000..7e17fd2
--- /dev/null
+++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/registeredOfficeAddress.json
@@ -0,0 +1,126 @@
+{
+ "definitions": {
+ "registeredOfficeAddress": {
+ "title": "registeredOfficeAddress",
+ "type": "object",
+ "required": [
+ "premises",
+ "address_line_1",
+ "locality",
+ "country",
+ "accept_appropriate_office_address_statement",
+ "postal_code"
+ ],
+ "properties": {
+ "etag": {
+ "type": "string",
+ "description": "The ETag of the resource.",
+ "readOnly": true
+ },
+ "kind": {
+ "type": "string",
+ "description": "The type of resource.",
+ "enum": [
+ "registered-office-address"
+ ],
+ "readOnly": true
+ },
+ "links": {
+ "type": "object",
+ "description": "Links to the related resources",
+ "items": {
+ "$ref": "genericModels.json#/definitions/selfLink"
+ },
+ "readOnly": true
+ },
+ "premises": {
+ "type": "string",
+ "description": "The property name or number."
+ },
+ "address_line_1": {
+ "type": "string",
+ "description": "The first line of the address."
+ },
+ "address_line_2": {
+ "type": "string",
+ "description": "The second line of the address."
+ },
+ "locality": {
+ "type": "string",
+ "description": "The locality e.g London."
+ },
+ "region": {
+ "type": "string",
+ "description": "The region e.g Surrey."
+ },
+ "postal_code": {
+ "type": "string",
+ "description": "The postal code e.g CF14 3UZ."
+ },
+ "country": {
+ "type": "string",
+ "description": "The country.",
+ "enum": [
+ "England",
+ "Wales",
+ "Scotland",
+ "Northern Ireland",
+ "Great Britain",
+ "United Kingdom",
+ "Not specified"
+ ]
+ },
+ "accept_appropriate_office_address_statement": {
+ "type": "boolean",
+ "description": "Setting this to true confirms that the new registered office address is an appropriate address as outlined in section 86(2) of the Companies Act 2006."
+ }
+ }
+ },
+ "registeredOfficeAddressChange": {
+ "title": "registeredOfficeAddressChange",
+ "type": "object",
+ "required": [
+ "reference_etag"
+ ],
+ "allOf": [{
+ "$ref": "registeredOfficeAddress.json#/definitions/registeredOfficeAddress"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "reference_etag": {
+ "type": "string",
+ "description": "The latest etag read from the current ROA API resource (`/company/{company_number}/registered-office-address`) on the public register. If this reference etag does not match the current register the request will be rejected."
+ },
+ "links": {
+ "type": "object",
+ "description": "Links to the related resources",
+ "items": {
+ "$ref": "registeredOfficeAddress.json#/definitions/registeredOfficeAddressChangeLinks"
+ },
+ "readOnly": true
+ }
+ }
+ }
+ ]
+ },
+ "registeredOfficeAddressChangeLinks": {
+ "title": "registeredOfficeAddressChangeLinks",
+ "type": "object",
+ "allOf": [{
+ "$ref": "genericModels.json#/definitions/selfLink"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "validation_status": {
+ "type": "string",
+ "description": "The URL of the validation status resource for the resource.",
+ "readOnly": true
+ }
+ }
+ }
+ ]
+ }
+ }
+}
diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/charges.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/charges.json
new file mode 100644
index 0000000..34eedc8
--- /dev/null
+++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/charges.json
@@ -0,0 +1,460 @@
+{
+ "chargeList": {
+ "get": {
+ "summary": "Charges",
+ "description": "List of charges for a company.",
+ "x-operationName": "list",
+ "tags": [
+ "charges"
+ ],
+ "parameters": [
+ {
+ "name": "company_number",
+ "in": "path",
+ "description": "The company number that the charge list is required for.",
+ "paramType": "path",
+ "required": true,
+ "type": "string"
+ },
+ {
+ "title": "items_per_page",
+ "description": "The number of charges to return per page.",
+ "type": "integer",
+ "paramType": "query",
+ "required": false
+ },
+ {
+ "title": "start_index",
+ "description": "The index into the entire result set that this result page starts.",
+ "type": "integer",
+ "paramType": "query",
+ "required": false
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Resource returned",
+ "schema": {
+ "$ref": "charges.json#/definitions/chargeList"
+ }
+ },
+ "401": {
+ "description": "Unauthorised"
+ },
+ "404": {
+ "description": "Resource not found"
+ }
+ }
+ }
+ },
+ "chargeDetails": {
+ "get": {
+ "description": "Individual charge information for company.",
+ "tags": [
+ "charges"
+ ],
+ "parameters": [
+ {
+ "name": "company_number",
+ "description": "The company number that the charge is required for.",
+ "paramType": "path",
+ "required": true,
+ "type": "string"
+ },
+ {
+ "name": "charge_id",
+ "description": "The id of the charge details that are required.",
+ "paramType": "path",
+ "required": true,
+ "type": "string"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Resource returned",
+ "schema": {
+ "$ref": "charges.json#/definitions/chargeDetails"
+ }
+ },
+ "401": {
+ "description": "Unauthorised"
+ },
+ "404": {
+ "description": "Resource not found"
+ }
+ }
+ }
+ },
+ "definitions": {
+ "chargeList": {
+ "title": "chargeList",
+ "required": [
+ "etag",
+ "items"
+ ],
+ "properties": {
+ "etag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ },
+ "total_count": {
+ "type": "integer",
+ "description": "Total number of charges returned by the API (filtering applies)."
+ },
+ "unfiletered_count": {
+ "type": "integer",
+ "description": "Number of satisfied charges"
+ },
+ "satisfied_count": {
+ "type": "integer",
+ "description": "Number of satisfied charges"
+ },
+ "part_satisfied_count": {
+ "type": "integer",
+ "description": "Number of satisfied charges"
+ },
+ "items": {
+ "type": "array",
+ "description": "List of charges",
+ "items": {
+ "$ref": "charges.json#/definitions/chargeDetails"
+ }
+ }
+ }
+ },
+ "chargeDetails": {
+ "title": "chargeDetails",
+ "required": [
+ "etag",
+ "status",
+ "classification",
+ "charge_number",
+ "id"
+ ],
+ "properties": {
+ "etag": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string",
+ "description": "The id of the charge"
+ },
+ "charge_code": {
+ "type": "string",
+ "description": "The charge code is a replacement of the mortgage description"
+ },
+ "classification": {
+ "type": "array",
+ "description": "Classification information",
+ "items": {
+ "$ref": "charges.json#/definitions/classificationDesc"
+ }
+ },
+ "charge_number": {
+ "type": "integer",
+ "description": "The charge number is used to reference an individual charge"
+ },
+ "status": {
+ "enum": [
+ "outstanding",
+ "fully-satisfied",
+ "part-satisfied",
+ "satisfied"
+ ],
+ "type": "string",
+ "description": "The status of the charge.\n For enumeration descriptions see `status` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/mortgage_descriptions.yml)"
+ },
+ "assests_ceased_released": {
+ "enum": [
+ "property-ceased-to-belong",
+ "part-property-release-and-ceased-to-belong",
+ "part-property-released",
+ "part-property-ceased-to-belong",
+ "whole-property-released",
+ "multiple-filings",
+ "whole-property-released-and-ceased-to-belong"
+ ],
+ "type": "string",
+ "description": "Cease/release information about the charge.\n For enumeration descriptions see `assets-ceased-released` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/mortgage_descriptions.yml)"
+ },
+ "acquired_on": {
+ "type": "string",
+ "format": "date",
+ "description": "The date the property or undertaking was acquired on"
+ },
+ "delivered_on": {
+ "type": "string",
+ "format": "date",
+ "description": "The date the charge was submitted to Companies House"
+ },
+ "resolved_on": {
+ "type": "string",
+ "format": "date",
+ "description": "The date the issue was resolved on"
+ },
+ "covering_instrument_date": {
+ "type": "string",
+ "format": "date",
+ "description": "The date by which the series of debentures were created"
+ },
+ "created_on": {
+ "type": "string",
+ "format": "date",
+ "description": "The date the charge was created"
+ },
+ "satisfied_on": {
+ "type": "string",
+ "format": "date",
+ "description": "The date the charge was satisfied"
+ },
+ "particulars": {
+ "type": "array",
+ "description": "Details of charge or undertaking",
+ "items": {
+ "$ref": "charges.json#/definitions/particularDesc"
+ }
+ },
+ "secured_details": {
+ "type": "array",
+ "description": "Information about what is secured against this charge",
+ "items": {
+ "$ref": "charges.json#/definitions/securedDetailsDesc"
+ }
+ },
+ "scottish_alterations": {
+ "type": "array",
+ "items": {
+ "$ref": "charges.json#/definitions/alterationsDesc"
+ },
+ "description": "Information about alterations for Scottish companies"
+ },
+ "more_than_four_persons_entitled": {
+ "type": "boolean",
+ "description": "Charge has more than four person entitled"
+ },
+ "persons_entitled": {
+ "type": "array",
+ "description": "People that are entitled to the charge",
+ "items": {
+ "$ref": "charges.json#/definitions/persons_entitled"
+ }
+ },
+ "transactions": {
+ "type": "array",
+ "description": "Transactions that have been filed for the charge.",
+ "items": {
+ "$ref": "charges.json#/definitions/transactions"
+ }
+ },
+ "insolvency_cases": {
+ "type": "array",
+ "description": "Transactions that have been filed for the charge.",
+ "items": {
+ "$ref": "charges.json#/definitions/insolvency_cases"
+ }
+ },
+ "links": {
+ "type": "array",
+ "description": "The resources related to this charge",
+ "items": {
+ "$ref": "charges.json#/definitions/charge_links"
+ }
+ }
+ }
+ },
+ "alterationsDesc": {
+ "title": "alterationsDesc",
+ "required": [
+ "type",
+ "description"
+ ],
+ "properties": {
+ "has_alterations_to_order": {
+ "type": "boolean",
+ "description": "The charge has alterations to order"
+ },
+ "has_alterations_to_prohibitions": {
+ "type": "boolean",
+ "description": "The charge has alterations to prohibitions"
+ },
+ "has_alterations_to_provisions": {
+ "type": "boolean",
+ "description": "The charge has provisions restricting the creation of further charges"
+ }
+ }
+ },
+ "securedDetailsDesc": {
+ "title": "securedDetailsDesc",
+ "required": [
+ "type",
+ "description"
+ ],
+ "properties": {
+ "type": {
+ "enum": [
+ "amount-secured",
+ "obligations-secured"
+ ],
+ "type": "string",
+ "description": "The type of secured details.\n For enumeration descriptions see `secured-details-description` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/mortgage_descriptions.yml)"
+ },
+ "description": {
+ "type": "string",
+ "description": "Details of the amount or obligation secured by the charge"
+ }
+ }
+ },
+ "particularDesc": {
+ "title": "particularDesc",
+ "required": [
+ "type",
+ "description"
+ ],
+ "properties": {
+ "type": {
+ "enum": [
+ "short-particulars",
+ "charged-property-description",
+ "charged-property-or-undertaking-description",
+ "brief-description"
+ ],
+ "type": "string",
+ "description": "The type of charge particulars.\n For enumeration descriptions see `particular-description` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/mortgage_descriptions.yml)"
+ },
+ "description": {
+ "type": "string",
+ "description": "Details of charge particulars"
+ },
+ "contains_floating_charge": {
+ "type": "boolean",
+ "description": "The charge contains a floating charge"
+ },
+ "contains_fixed_charge": {
+ "type": "boolean",
+ "description": "The charge contains a fixed charge"
+ },
+ "floating_charge_covers_all": {
+ "type": "boolean",
+ "description": "The floating charge covers all the property or undertaking or the company"
+ },
+ "contains_negative_pledge": {
+ "type": "boolean",
+ "description": "The charge contains a negative pledge"
+ },
+ "chargor_acting_as_bare_trustee": {
+ "type": "boolean",
+ "description": "The chargor is acting as a bare trustee for the property"
+ }
+ }
+ },
+ "classificationDesc": {
+ "title": "classificationDesc",
+ "required": [
+ "type",
+ "description"
+ ],
+ "properties": {
+ "type": {
+ "enum": [
+ "charge-description",
+ "nature-of-charge"
+ ],
+ "type": "string",
+ "description": "The type of charge classication.\n For enumeration descriptions see `classificationDesc` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/mortgage_descriptions.yml)"
+ },
+ "description": {
+ "type": "string",
+ "description": "Details of the charge classification"
+ }
+ }
+ },
+ "persons_entitled": {
+ "title": "persons_entitled",
+ "required": [
+ "name"
+ ],
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "The name of the person entitled."
+ }
+ }
+ },
+ "transactions": {
+ "title": "transactions",
+ "properties": {
+ "filing_type": {
+ "type": "string",
+ "description": "Filing type which created, updated or satisfied the charge"
+ },
+ "delivered_on": {
+ "type": "string",
+ "format": "date",
+ "description": "The date the filing was submitted to Companies House"
+ },
+ "insolvency_case_number": {
+ "type": "string",
+ "description": "The insolvency case related to this filing"
+ },
+ "links": {
+ "type": "array",
+ "description": "The resources related to this filing",
+ "items": {
+ "$ref": "charges.json#/definitions/transaction_links"
+ }
+ }
+ }
+ },
+ "transaction_links": {
+ "title": "transaction_links",
+ "properties": {
+ "filing": {
+ "type": "string",
+ "description": "Link to the charge filing data"
+ },
+ "insolvency_case": {
+ "type": "string",
+ "description": "Link to the insolvency case related to this filing"
+ }
+ }
+ },
+ "insolvency_cases": {
+ "title": "insolvency_cases",
+ "properties": {
+ "case_number": {
+ "type": "string",
+ "description": "The number of this insolvency case"
+ },
+ "links": {
+ "type": "array",
+ "description": "The resources related to this insolvency case",
+ "items": {
+ "$ref": "charges.json#/definitions/insolvency_case_links"
+ }
+ }
+ }
+ },
+ "insolvency_case_links": {
+ "title": "insolvency_case_links",
+ "properties": {
+ "case": {
+ "type": "string",
+ "description": "Link to the insolvency case data"
+ }
+ }
+ },
+ "charge_links": {
+ "title": "charge_links",
+ "required": [
+ "self"
+ ],
+ "properties": {
+ "self": {
+ "type": "string",
+ "description": "Link to the this charge data"
+ }
+ }
+ }
+ }
+}
+
diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyAddress.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyAddress.json
new file mode 100644
index 0000000..e24eedb
--- /dev/null
+++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyAddress.json
@@ -0,0 +1,255 @@
+{
+ "getCompanyAddress": {
+ "get": {
+ "summary": "Registered Office Address",
+ "description": "Get the current address of a company",
+ "parameters": [{
+ "name": "company_number",
+ "in": "path",
+ "description": "Company number for registered office address",
+ "required": true,
+ "type": "string"
+ }],
+ "tags": [
+ "registeredOfficeAddress"
+ ],
+ "security": [{
+ "oauth2": [
+ "https://identity.company-information.service.gov.uk/user/profile.read",
+ "https://api.company-information.service.gov.uk/company/{company_number}/registered-office-address.update"
+ ]
+ }],
+ "responses": {
+ "200": {
+ "description": "Successful response",
+ "schema": {
+ "$ref": "../models/registeredOfficeAddress.json#/definitions/registeredOfficeAddress"
+ }
+ },
+ "401": {
+ "description": "Not authorised"
+ },
+ "404": {
+ "description": "Resource not found"
+ }
+ }
+ }
+ },
+ "addressTransactions": {
+ "get": {
+ "summary": "Get a registered office address resource",
+ "description": "Get registered office address resource",
+ "tags": [
+ "registeredOfficeAddress"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "parameters": [{
+ "name": "transaction_id",
+ "in": "path",
+ "type": "string",
+ "description": "transaction id",
+ "required": true
+ }],
+ "security": [{
+ "oauth2": [
+ "https://identity.company-information.service.gov.uk/user/profile.read",
+ "https://api.company-information.service.gov.uk/company/{company_number}/registered-office-address.update"
+ ]
+ }],
+ "responses": {
+ "200": {
+ "description": "Registered office address resource",
+ "schema": {
+ "$ref": "../models/registeredOfficeAddress.json#/definitions/registeredOfficeAddressChange"
+ }
+ },
+ "401": {
+ "description": "Not authorised to get the registered office address resource"
+ },
+ "404": {
+ "description": "Resource not found"
+ }
+ }
+ },
+ "put": {
+ "summary": "Replace a registered office address resource",
+ "description": "Replace a registered office address resource. If filing with the Insolvency scope “company_number” must be provided in the request body.",
+ "tags": [
+ "registeredOfficeAddress"
+ ],
+ "consumes": [
+ "application/json"
+ ],
+ "parameters": [{
+ "name": "transaction_id",
+ "in": "path",
+ "type": "string",
+ "description": "transaction id",
+ "required": true
+ },
+ {
+ "name": "request",
+ "in": "body",
+ "description": "The writable fields of the registered office address resource",
+ "required": true,
+ "schema": {
+ "$ref": "../models/registeredOfficeAddress.json#/definitions/registeredOfficeAddressChange"
+ }
+ }
+ ],
+ "security": [{
+ "oauth2": [
+ "https://identity.company-information.service.gov.uk/user/profile.read",
+ "https://api.company-information.service.gov.uk/company/{company_number}/registered-office-address.update"
+ ]
+ }],
+ "responses": {
+ "200": {
+ "description": "Registered office address resource updated",
+ "schema": {
+ "$ref": "../models/registeredOfficeAddress.json#/definitions/registeredOfficeAddressChange"
+ }
+ },
+ "401": {
+ "description": "Not authorised to update this transaction"
+ },
+ "403": {
+ "description": "Registered office address resource cannot be updated as it's containing transaction has been closed"
+ },
+ "404": {
+ "description": "Transaction not found"
+ }
+ }
+ },
+ "post": {
+ "summary": "Create a registered office address resource",
+ "description": "Effective 15 September 2025, the postcode of the registered office address will be a mandatory field for the \nCompanies House Service (CHS) Filed AD01 form used to change a company’s registered office address.\n\nCreate a registered office address resource will require the “postal_code” field in the request body from this date. \nTo prepare, please ensure your implementation of the registered office address resource includes a “postal_code” field in all relevant requests.",
+ "tags": [
+ "registeredOfficeAddress"
+ ],
+ "x-operationName": "create",
+ "parameters": [{
+ "name": "transaction_id",
+ "in": "path",
+ "type": "string",
+ "description": "transaction id",
+ "required": true
+ },
+ {
+ "name": "request",
+ "in": "body",
+ "description": "The writable fields of the registered office address resource",
+ "required": false,
+ "schema": {
+ "$ref": "../models/registeredOfficeAddress.json#/definitions/registeredOfficeAddressChange"
+ }
+ }
+ ],
+ "security": [{
+ "oauth2": [
+ "https://identity.company-information.service.gov.uk/user/profile.read",
+ "https://api.company-information.service.gov.uk/company/{company_number}/registered-office-address.update"
+ ]
+ }],
+ "responses": {
+ "201": {
+ "description": "Registered office address resource created within transaction envelope",
+ "schema": {
+ "$ref": "../models/registeredOfficeAddress.json#/definitions/registeredOfficeAddressChange"
+ }
+ },
+ "400": {
+ "description": "Bad request"
+ },
+ "401": {
+ "description": "Not authorised to create a registered office address resource within this transaction"
+ },
+ "403": {
+ "description": "Registered office address resource cannot be created as it's containing transaction has been closed"
+ },
+ "404": {
+ "description": "Transaction not found"
+ },
+ "409": {
+ "description": "Conflict. Occurs when a registered office address already exists for the given transaction"
+ }
+ }
+ }
+ },
+ "addressTransactionsValidation": {
+ "get": {
+ "summary": "Get validation status for a registered office address resource",
+ "description": "Get validation status for registered office address resource",
+ "tags": [
+ "registeredOfficeAddress"
+ ],
+ "x-operationName": "validate",
+ "produces": [
+ "application/json"
+ ],
+ "parameters": [{
+ "name": "transaction_id",
+ "in": "path",
+ "type": "string",
+ "description": "transaction id",
+ "required": true
+ }],
+ "security": [{
+ "oauth2": [
+ "https://identity.company-information.service.gov.uk/user/profile.read",
+ "https://api.company-information.service.gov.uk/company/{company_number}/registered-office-address.update"
+ ]
+ }],
+ "responses": {
+ "200": {
+ "description": "Validation status for a registered office address resource.",
+ "schema": {
+ "$ref": "../models/errors.json#/definitions/validationStatus"
+ }
+ },
+ "401": {
+ "description": "Not authorised to get the registered office address resource"
+ },
+ "404": {
+ "description": "Resource not found"
+ }
+ }
+ }
+ },
+ "filingGenerator": {
+ "get": {
+ "summary": "Get registered office address filing",
+ "description": "Generate and return registered office address filing",
+ "tags": [
+ "filing-generator"
+ ],
+ "x-operationName": "generate registered office address filing",
+ "security": [{
+ "oauth2": [
+ "https://identity.company-information.service.gov.uk/user/profile.read",
+ "https://api.company-information.service.gov.uk/company/{company_number}/registered-office-address.update"
+ ]
+ }],
+ "responses": {
+ "200": {
+ "description": "Generated filings successfully returned",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "../models/filings.json#/definitions/filing"
+ }
+ }
+ },
+ "400": {
+ "description": "Bad request",
+ "schema": {
+ "$ref": "errorModel.json#/definitions/error"
+ }
+ }
+ }
+ }
+ }
+}
+
diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyOfficerList.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyOfficerList.json
new file mode 100644
index 0000000..3718d12
--- /dev/null
+++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyOfficerList.json
@@ -0,0 +1,541 @@
+{
+ "listCompanyOfficers": {
+ "get": {
+ "summary": "Company Officers",
+ "description": "List of all company officers",
+ "x-operationName": "list",
+ "tags": [
+ "officers"
+ ],
+ "parameters": [{
+ "name": "company_number",
+ "in": "path",
+ "description": "The company number of the officer list being requested.",
+ "required": true,
+ "type": "string"
+ },
+ {
+ "name": "items_per_page",
+ "in": "query",
+ "description": "The number of officers to return per page.",
+ "type": "integer"
+ },
+ {
+ "name": "register_type",
+ "in": "query",
+ "description": "The register_type determines which officer type is returned for the registers view.The register_type field will only work if registers_view is set to true",
+ "type": "string",
+ "enum": [
+ "directors",
+ "secretaries",
+ "llp_members"
+ ]
+ },
+ {
+ "name": "register_view",
+ "in": "query",
+ "description": "Display register specific information. If given register is held at Companies House, registers_view set to true and correct register_type specified, only active officers will be returned. Defaults to false",
+ "type": "string",
+ "enum": [
+ "true",
+ "false"
+ ]
+ },
+ {
+ "name": "start_index",
+ "in": "query",
+ "description": "The offset into the entire result set that this page starts.",
+ "type": "integer"
+ },
+ {
+ "name": "order_by",
+ "in": "query",
+ "description": "The field by which to order the result set.",
+ "type": "string",
+ "enum": [
+ "appointed_on",
+ "resigned_on",
+ "surname"
+ ]
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "List the company officers",
+ "headers": {
+ "ETag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ }
+ },
+ "schema": {
+ "$ref": "companyOfficerList.json#/definitions/officerList"
+ }
+ },
+ "400": {
+ "description": "Bad request",
+ "schema": {
+ "$ref": "errorModel.json#/definitions/error"
+ }
+ },
+ "401": {
+ "description": "Unauthorised"
+ }
+ }
+ }
+ },
+ "getCompanyOfficerAppointment": {
+ "get": {
+ "summary": "Get a company officer appointment",
+ "description": "Get details of an individual company officer appointment",
+ "tags": [
+ "officers"
+ ],
+ "parameters": [{
+ "name": "company_number",
+ "in": "path",
+ "description": "The company number of the officer list being requested.",
+ "required": true,
+ "type": "string"
+ },
+ {
+ "name": "appointment_id",
+ "in": "path",
+ "description": "The appointment id of the company officer appointment being requested.",
+ "required": true,
+ "type": "string"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Get a company officer appointment",
+ "headers": {
+ "ETag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ }
+ },
+ "schema": {
+ "$ref": "companyOfficerList.json#/definitions/officerSummary"
+ }
+ },
+ "400": {
+ "description": "Bad request",
+ "schema": {
+ "$ref": "errorModel.json#/definitions/error"
+ }
+ },
+ "401": {
+ "description": "Unauthorised"
+ }
+ }
+ }
+ },
+ "definitions": {
+ "address": {
+ "title": "address",
+ "properties": {
+ "address_line_1": {
+ "description": "The first line of the address.",
+ "type": "string"
+ },
+ "address_line_2": {
+ "description": "The second line of the address.",
+ "type": "string"
+ },
+ "care_of": {
+ "description": "The care of name.",
+ "type": "string"
+ },
+ "country": {
+ "description": "The country e.g. United Kingdom.",
+ "type": "string"
+ },
+ "locality": {
+ "description": "The locality e.g. London.",
+ "type": "string"
+ },
+ "po_box": {
+ "description": "The post-office box number.",
+ "type": "string"
+ },
+ "postal_code": {
+ "description": "The postal code e.g. CF14 3UZ.",
+ "type": "string"
+ },
+ "premises": {
+ "description": "The property name or number.",
+ "type": "string"
+ },
+ "region": {
+ "description": "The region e.g. Surrey.",
+ "type": "string"
+ }
+ }
+ },
+ "contactDetails": {
+ "title": "contactDetails",
+ "properties": {
+ "contact_name": {
+ "description": "The name of the contact.",
+ "type": "string"
+ }
+ }
+ },
+ "officerList": {
+ "title": "officerList",
+ "properties": {
+ "active_count": {
+ "description": "The number of active officers in this result set.",
+ "type": "integer"
+ },
+ "etag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ },
+ "items": {
+ "description": "The list of officers.",
+ "items": {
+ "$ref": "companyOfficerList.json#/definitions/officerSummary"
+ },
+ "type": "array"
+ },
+ "items_per_page": {
+ "description": "The number of officers to return per page.",
+ "type": "integer"
+ },
+ "kind": {
+ "enum": [
+ "officer-list"
+ ],
+ "type": "string"
+ },
+ "links": {
+ "description": "Links to other resources associated with this officer list resource.",
+ "items": {
+ "$ref": "companyOfficerList.json#/definitions/linkTypes"
+ },
+ "type": "object"
+ },
+ "resigned_count": {
+ "description": "The number of resigned officers in this result set.",
+ "type": "integer"
+ },
+ "start_index": {
+ "description": "The offset into the entire result set that this page starts.",
+ "type": "integer"
+ },
+ "total_results": {
+ "description": "The total number of officers in this result set.",
+ "type": "integer"
+ }
+ },
+ "required": [
+ "etag",
+ "items_per_page",
+ "kind",
+ "links",
+ "items",
+ "start_index",
+ "total_results",
+ "active_count",
+ "resigned_count"
+ ]
+ },
+ "officerSummary": {
+ "title": "officerSummary",
+ "properties": {
+ "address": {
+ "description": "The correspondence address of the officer.",
+ "items": {
+ "$ref": "companyOfficerList.json#/definitions/address"
+ },
+ "type": "object"
+ },
+ "appointed_on": {
+ "description": "The date on which the officer was appointed. For the officer roles of `corporate-managing-officer` and `managing-officer` this is the date on which Companies House was notified about the officer.",
+ "type": "string",
+ "format": "date"
+ },
+ "contact_details": {
+ "description": "The contact at the `corporate-managing-officer` of a `registered-overseas-entity`.",
+ "items": {
+ "$ref": "companyOfficerList.json#/definitions/contactDetails"
+ },
+ "type": "object"
+ },
+ "country_of_residence": {
+ "description": "The officer's country of residence.",
+ "type": "string"
+ },
+ "date_of_birth": {
+ "description": "Details of director date of birth.",
+ "items": {
+ "$ref": "companyOfficerList.json#/definitions/dateOfBirth"
+ },
+ "type": "object"
+ },
+ "links": {
+ "description": "Links to other resources associated with this officer list item.",
+ "items": {
+ "$ref": "companyOfficerList.json#/definitions/itemLinkTypes"
+ },
+ "type": "object"
+ },
+ "name": {
+ "description": "Corporate or natural officer name.",
+ "type": "string"
+ },
+ "nationality": {
+ "description": "The officer's nationality.",
+ "type": "string"
+ },
+ "occupation": {
+ "description": "The officer's job title.",
+ "type": "string"
+ },
+ "officer_role": {
+ "enum": [
+ "cic-manager",
+ "corporate-director",
+ "corporate-llp-designated-member",
+ "corporate-llp-member",
+ "corporate-manager-of-an-eeig",
+ "corporate-managing-officer",
+ "corporate-member-of-a-management-organ",
+ "corporate-member-of-a-supervisory-organ",
+ "corporate-member-of-an-administrative-organ",
+ "corporate-nominee-director",
+ "corporate-nominee-secretary",
+ "corporate-secretary",
+ "director",
+ "general-partner-in-a-limited-partnership",
+ "judicial-factor",
+ "limited-partner-in-a-limited-partnership",
+ "llp-designated-member",
+ "llp-member",
+ "manager-of-an-eeig",
+ "managing-officer",
+ "member-of-a-management-organ",
+ "member-of-a-supervisory-organ",
+ "member-of-an-administrative-organ",
+ "nominee-director",
+ "nominee-secretary",
+ "person-authorised-to-accept",
+ "person-authorised-to-represent",
+ "person-authorised-to-represent-and-accept",
+ "receiver-and-manager",
+ "secretary"
+ ],
+ "type": "string"
+ },
+ "person_number" : {
+ "description" : "Unique person identifier as displayed in bulk products 195, 198, 208, 209 and 216.",
+ "type" : "string"
+ },
+ "principal_office_address": {
+ "description": "The principal/registered office address of a `corporate-managing-officer` of a `registered-overseas-entity`.",
+ "items": {
+ "$ref": "companyOfficerList.json#/definitions/principalOfficeAddress"
+ },
+ "type": "object"
+ },
+ "resigned_on": {
+ "description": "The date the officer was resigned. For the officer roles of `corporate-managing-officer` and `managing-officer` this is the date on which Companies House was notified about the officers cessation.",
+ "type": "string",
+ "format": "date"
+ },
+ "responsibilities": {
+ "description": "The responsibilities of the managing officer of a `registered-overseas-entity`.",
+ "type": "string"
+ },
+ "former_names": {
+ "description": "Former names for the officer.",
+ "items": {
+ "$ref": "companyOfficerList.json#/definitions/formerNames"
+ },
+ "type": "array"
+ },
+ "identification": {
+ "description": "Only one from `eea`, `non-eea`, `uk-limited-company`, `other-corporate-body-or-firm` or `registered-overseas-entity-corporate-managing-officer` can be supplied, not multiples of them.",
+ "items": {
+ "$ref": "companyOfficerList.json#/definitions/corporateIdent"
+ },
+ "type": "object"
+ },
+ "identity_verification_details": {
+ "description": "Information relating to the identity verification of the officer",
+ "items": {
+ "$ref": "../models/officerChanges.json#/definitions/identityVerificationDetails"
+ },
+ "type": "object"
+ },
+ "appointed_before": {
+ "description": "The date the officer was appointed before. Only present when the is_pre_1992_appointment attribute is true.",
+ "type": "string"
+ },
+ "etag": {
+ "description": "The Etag of the resource",
+ "type": "string"
+ },
+ "is_pre_1992_appointment": {
+ "description": "Indicator representing if the officer was appointed before their appointment date.",
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "links",
+ "name",
+ "officer_role"
+ ]
+ },
+ "itemLinkTypes": {
+ "title": "itemLinkTypes",
+ "required": [
+ "self",
+ "officer"
+ ],
+ "properties": {
+ "self": {
+ "description": "Link to this individual company officer appointment resource.",
+ "type": "string"
+ },
+ "officer": {
+ "description": "Links to other officer resources associated with this officer list item.",
+ "items": {
+ "$ref": "companyOfficerList.json#/definitions/officerLinkTypes"
+ },
+ "type": "object"
+ }
+ }
+ },
+ "linkTypes": {
+ "title": "linkTypes",
+ "required": [
+ "self"
+ ],
+ "properties": {
+ "self": {
+ "description": "Link to this officer list resource.",
+ "type": "string"
+ }
+ }
+ },
+ "officerLinkTypes": {
+ "title": "officerLinkTypes",
+ "required": [
+ "appointments"
+ ],
+ "properties": {
+ "appointments": {
+ "description": "Link to the officer appointment resource that this appointment is associated with.",
+ "type": "string"
+ }
+ }
+ },
+ "formerNames": {
+ "title": "formerNames",
+ "properties": {
+ "forenames": {
+ "description": "Former forenames of the officer.",
+ "type": "string"
+ },
+ "surname": {
+ "description": "Former surnames of the officer.",
+ "type": "string"
+ }
+ }
+ },
+ "corporateIdent": {
+ "title": "corporateIdent",
+ "properties": {
+ "identification_type": {
+ "description": "The officer's identity type",
+ "enum": [
+ "eea",
+ "non-eea",
+ "uk-limited-company",
+ "other-corporate-body-or-firm",
+ "registered-overseas-entity-corporate-managing-officer"
+ ],
+ "type": "string"
+ },
+ "legal_authority": {
+ "description": "The legal authority supervising the company.",
+ "type": "string"
+ },
+ "legal_form": {
+ "description": "The legal form of the company as defined by its country of registration.",
+ "type": "string"
+ },
+ "place_registered": {
+ "description": "Place registered.",
+ "type": "string"
+ },
+ "registration_number": {
+ "description": "Company registration number.",
+ "type": "string"
+ }
+ }
+ },
+ "dateOfBirth": {
+ "title": "dateOfBirth",
+ "properties": {
+ "month": {
+ "description": "The month of date of birth.",
+ "type": "integer"
+ },
+ "year": {
+ "description": "The year of date of birth.",
+ "type": "integer"
+ }
+ },
+ "required": [
+ "month",
+ "year"
+ ]
+ },
+ "principalOfficeAddress": {
+ "title": "principalOfficeAddress",
+ "properties": {
+ "address_line_1": {
+ "description": "The first line of the address.",
+ "type": "string"
+ },
+ "address_line_2": {
+ "description": "The second line of the address.",
+ "type": "string"
+ },
+ "care_of": {
+ "description": "The care of name.",
+ "type": "string"
+ },
+ "country": {
+ "description": "The country e.g. United Kingdom.",
+ "type": "string"
+ },
+ "locality": {
+ "description": "The locality e.g. London.",
+ "type": "string"
+ },
+ "po_box": {
+ "description": "The post-office box number.",
+ "type": "string"
+ },
+ "postal_code": {
+ "description": "The postal code e.g. CF14 3UZ.",
+ "type": "string"
+ },
+ "premises": {
+ "description": "The property name or number.",
+ "type": "string"
+ },
+ "region": {
+ "description": "The region e.g. Surrey.",
+ "type": "string"
+ }
+ }
+ }
+ }
+}
+
diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyProfile.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyProfile.json
new file mode 100644
index 0000000..df963fe
--- /dev/null
+++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyProfile.json
@@ -0,0 +1,822 @@
+{
+ "get": {
+ "summary": "Company profile",
+ "description": "Get the basic company information",
+ "parameters": [{
+ "name": "company_number",
+ "in": "path",
+ "description": "The company number of the basic information to return.",
+ "required": true,
+ "type": "string"
+ }],
+ "tags": [
+ "companyProfile"
+ ],
+ "responses": {
+ "200": {
+ "description": "readCompanyProfile",
+ "headers": {
+ "ETag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ }
+ },
+ "schema": {
+ "$ref": "companyProfile.json#/definitions/companyProfile"
+ }
+ },
+ "401": {
+ "description": "Unauthorised"
+ },
+ "404": {
+ "description": "Resource not found"
+ }
+ }
+ },
+ "definitions": {
+ "accountingReferenceDate": {
+ "title": "accountingReferenceDate",
+ "type": "object",
+ "required": [
+ "day",
+ "month"
+ ],
+ "properties": {
+ "day": {
+ "type": "integer",
+ "description": "The Accounting Reference Date (ARD) day."
+ },
+ "month": {
+ "type": "integer",
+ "description": "The Accounting Reference Date (ARD) month."
+ }
+ }
+ },
+ "accountsInformation": {
+ "title": "accountsInformation",
+ "type": "object",
+ "required": [
+ "overdue",
+ "next_made_up_to",
+ "accounting_reference_date"
+ ],
+ "properties": {
+ "accounting_reference_date": {
+ "description": "The Accounting Reference Date (ARD) of the company.",
+ "type": "object",
+ "items": {
+ "$ref": "companyProfile.json#/definitions/accountingReferenceDate"
+ }
+ },
+ "last_accounts": {
+ "description": "The last company accounts filed.",
+ "type": "object",
+ "items": {
+ "$ref": "companyProfile.json#/definitions/lastAccounts"
+ }
+ },
+ "next_due": {
+ "description": "Deprecated. Please use accounts.next_accounts.due_on",
+ "type": "string",
+ "format": "date"
+ },
+ "next_made_up_to": {
+ "description": "Deprecated. Please use accounts.next_accounts.period_end_on",
+ "type": "string",
+ "format": "date"
+ },
+ "overdue": {
+ "type": "boolean",
+ "description": "Deprecated. Please use accounts.next_accounts.overdue"
+ },
+ "next_accounts": {
+ "description": "The next company accounts filed.",
+ "type": "object",
+ "items": {
+ "$ref": "companyProfile.json#/definitions/nextAccounts"
+ }
+ }
+
+ }
+ },
+ "annualReturnInformation": {
+ "title": "annualReturnInformation",
+ "type": "object",
+ "properties": {
+ "last_made_up_to": {
+ "description": "The date the last annual return was made up to.",
+ "type": "string",
+ "format": "date"
+ },
+ "next_due": {
+ "description": "The date the next annual return is due. This member will only be returned if a confirmation statement has not been filed and the date is before 28th July 2016, otherwise refer to `confirmation_statement.next_due`",
+ "type": "string",
+ "format": "date"
+ },
+ "next_made_up_to": {
+ "description": "The date the next annual return should be made up to. This member will only be returned if a confirmation statement has not been filed and the date is before 30th July 2016, otherwise refer to `confirmation_statement.next_made_up_to`",
+ "type": "string",
+ "format": "date"
+ },
+ "overdue": {
+ "description": "Flag indicating if the annual return is overdue.",
+ "type": "boolean"
+ }
+ }
+ },
+ "confirmationOfStatementInformation": {
+ "title": "confirmationOfStatementInformation",
+ "required": [
+ "next_made_up_to",
+ "next_due"
+ ],
+ "properties": {
+ "last_made_up_to": {
+ "description": "The date to which the company last made a confirmation statement.",
+ "type": "string",
+ "format": "date"
+ },
+ "next_due": {
+ "description": "The date by which the next confimation statement must be received.",
+ "type": "string",
+ "format": "date"
+ },
+ "next_made_up_to": {
+ "description": "The date to which the company must next make a confirmation statement.",
+ "type": "string",
+ "format": "date"
+ },
+ "overdue": {
+ "description": "Flag indicating if the confirmation statement is overdue",
+ "type": "boolean"
+ }
+ }
+ },
+ "companyProfile": {
+ "title": "companyProfile",
+ "required": [
+ "company_name",
+ "company_number",
+ "type",
+ "can_file",
+ "links"
+ ],
+ "properties": {
+ "accounts": {
+ "description": "Company accounts information.",
+ "items": {
+ "$ref": "companyProfile.json#/definitions/accountsInformation"
+ },
+ "type": "object"
+ },
+ "annual_return": {
+ "description": "Annual return information. This member is only returned if a confirmation statement has not be filed.",
+ "items": {
+ "$ref": "companyProfile.json#/definitions/annualReturnInformation"
+ },
+ "type": "object"
+ },
+ "can_file": {
+ "description": "Flag indicating whether this company can file.",
+ "type": "boolean"
+ },
+ "confirmation_statement": {
+ "description": "Confirmation statement information (N.B. refers to the Annual Statement where type is registered-overseas-entity)",
+ "items": {
+ "$ref": "companyProfile.json#/definitions/confirmationOfStatementInformation"
+ },
+ "type": "object"
+ },
+ "company_name": {
+ "description": "The name of the company.",
+ "type": "string"
+ },
+ "jurisdiction": {
+ "description": "The jurisdiction specifies the political body responsible for the company.",
+ "type": "string",
+ "enum": [
+ "england-wales",
+ "wales",
+ "scotland",
+ "northern-ireland",
+ "european-union",
+ "united-kingdom",
+ "england",
+ "noneu"
+ ]
+ },
+ "company_number": {
+ "description": "The number of the company.",
+ "type": "string"
+ },
+ "date_of_creation": {
+ "description": "The date when the company was created.",
+ "type": "string",
+ "format": "date"
+ },
+ "date_of_cessation": {
+ "description": "The date which the company was converted/closed, dissolved or removed. Please refer to company status to determine which.",
+ "type": "string",
+ "format": "date"
+ },
+ "etag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ },
+ "has_been_liquidated": {
+ "description": "Deprecated. Please use links.insolvency",
+ "type": "boolean"
+ },
+ "has_charges": {
+ "description": "Deprecated. Please use links.charges",
+ "type": "boolean"
+ },
+ "is_community_interest_company": {
+ "description": "Deprecated. Please use subtype",
+ "type": "boolean"
+ },
+ "subtype": {
+ "description": "The subtype of the company. Possible values are:",
+ "enum":[
+ "community-interest-company",
+ "private-fund-limited-partnership"
+ ],
+ "type": "string"
+ },
+ "partial_data_available": {
+ "description": "Returned if Companies House is not the primary source of data for this company.\nFor enumeration descriptions see partial_data_available section in the enumeration mappings (https://github.com/companieshouse/api-enumerations/blob/master/constants.yml).",
+ "enum":[
+ "full-data-available-from-financial-conduct-authority",
+ "full-data-available-from-department-of-the-economy",
+ "full-data-available-from-the-company"
+ ],
+ "type": "string"
+ },
+ "external_registration_number": {
+ "description": "The number given by an external registration body.",
+ "type": "string"
+ },
+ "foreign_company_details": {
+ "description": "Foreign company details.",
+ "items": {
+ "$ref": "companyProfile.json#/definitions/foreignCompanyDetails"
+ },
+ "type": "object"
+ },
+ "last_full_members_list_date": {
+ "description": "The date of last full members list update.",
+ "type": "string",
+ "format": "date"
+ },
+ "registered_office_address": {
+ "description": "The address of the company's registered office.",
+ "items": {
+ "$ref": "companyProfile.json#/definitions/registeredOfficeAddress"
+ },
+ "type": "object"
+ },
+ "service_address": {
+ "description": "The correspondence address of a Registered overseas entity",
+ "items": {
+ "$ref": "companyProfile.json#/definitions/serviceAddress"
+ },
+ "type": "object"
+ },
+ "super_secure_managing_officer_count": {
+ "description": "The total count of super secure managing officers for a `registered-overseas-entity`.",
+ "type": "integer"
+ },
+ "sic_codes": {
+ "description": "SIC codes for this company.",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "previous_company_names": {
+ "description": "The previous names of this company.",
+ "items": {
+ "$ref": "companyProfile.json#/definitions/previousCompanyNames"
+ },
+ "type": "array"
+ },
+ "corporate_annotation": {
+ "description": "A corporate level message published by Companies House about a company, or situations affecting the company, or its information.",
+ "items": {
+ "$ref": "companyProfile.json#/definitions/corporateAnnotation"
+ },
+ "type": "array"
+ },
+ "company_status": {
+ "description": "The status of the company. \n For enumeration descriptions see `company_status` section in the [enumeration mappings] (https://github.com/companieshouse/api-enumerations/blob/master/constants.yml) ",
+ "type": "string",
+ "enum": [
+ "active",
+ "dissolved",
+ "liquidation",
+ "receivership",
+ "administration",
+ "voluntary-arrangement",
+ "converted-closed",
+ "insolvency-proceedings",
+ "registered",
+ "removed",
+ "closed",
+ "open"
+ ]
+ },
+ "company_status_detail": {
+ "description": "Extra details about the status of the company. \n For enumeration descriptions see `company_status_detail` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/constants.yml). ",
+ "enum": [
+ "transferred-from-uk",
+ "active-proposal-to-strike-off",
+ "petition-to-restore-dissolved",
+ "transformed-to-se",
+ "converted-to-plc"
+ ],
+ "type": "string"
+ },
+ "type": {
+ "description": "The type of the company. \n For enumeration descriptions see `company_type` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/constants.yml) ",
+ "enum": [
+ "private-unlimited",
+ "ltd",
+ "plc",
+ "old-public-company",
+ "private-limited-guarant-nsc-limited-exemption",
+ "limited-partnership",
+ "private-limited-guarant-nsc",
+ "converted-or-closed",
+ "private-unlimited-nsc",
+ "private-limited-shares-section-30-exemption",
+ "protected-cell-company",
+ "assurance-company",
+ "oversea-company",
+ "eeig",
+ "icvc-securities",
+ "icvc-warrant",
+ "icvc-umbrella",
+ "registered-society-non-jurisdictional",
+ "industrial-and-provident-society",
+ "northern-ireland",
+ "northern-ireland-other",
+ "royal-charter",
+ "investment-company-with-variable-capital",
+ "unregistered-company",
+ "llp",
+ "other",
+ "european-public-limited-liability-company-se",
+ "uk-establishment",
+ "scottish-partnership",
+ "charitable-incorporated-organisation",
+ "scottish-charitable-incorporated-organisation",
+ "further-education-or-sixth-form-college-corporation",
+ "registered-overseas-entity"
+ ],
+ "type": "string"
+ },
+ "has_insolvency_history": {
+ "description": "Deprecated. Please use links.insolvency",
+ "type": "boolean"
+ },
+ "undeliverable_registered_office_address": {
+ "description": "Flag indicating whether post can be delivered to the registered office.",
+ "type": "boolean"
+ },
+ "registered_office_is_in_dispute": {
+ "description": "Flag indicating registered office address as been replaced.",
+ "type": "boolean"
+ },
+ "branch_company_details": {
+ "description": "UK branch of a foreign company.",
+ "items": {
+ "$ref": "companyProfile.json#/definitions/branchCompanyDetails"
+ }
+ },
+ "links": {
+ "description": "A set of URLs related to the resource, including self.",
+ "items": {
+ "$ref": "companyProfile.json#/definitions/linksType"
+ },
+ "type": "object"
+ }
+ }
+ },
+ "lastAccounts": {
+ "title": "lastAccounts",
+ "properties": {
+ "made_up_to": {
+ "type": "string",
+ "format": "date",
+ "description": "Deprecated. Please use accounts.last_accounts.period_end_on"
+ },
+ "period_end_on": {
+ "type": "string",
+ "format": "date",
+ "description": "The last day of the most recently filed accounting period."
+ },
+ "period_start_on": {
+ "type": "string",
+ "format": "date",
+ "description": "The first day of the most recently filed accounting period."
+ },
+ "type": {
+ "description": "The type of the last company accounts filed. \n For enumeration descriptions see `account_type` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/constants.yml). ",
+ "enum": [
+ "null",
+ "full",
+ "small",
+ "medium",
+ "group",
+ "dormant",
+ "interim",
+ "initial",
+ "total-exemption-full",
+ "total-exemption-small",
+ "partial-exemption",
+ "audit-exemption-subsidiary",
+ "filing-exemption-subsidiary",
+ "micro-entity",
+ "no-accounts-type-available",
+ "audited-abridged",
+ "unaudited-abridged"
+ ]
+ }
+ },
+ "type": "string",
+ "required": [
+ "type",
+ "made_up_to"
+ ]
+ },
+ "nextAccounts": {
+ "title": "nextAccounts",
+ "type": "object",
+ "properties": {
+ "due_on": {
+ "description": "The date the next company accounts are due",
+ "type": "string",
+ "format": "date"
+ },
+ "overdue": {
+ "description": "Flag indicating if the company accounts are overdue.",
+ "type": "boolean"
+ },
+ "period_end_on": {
+ "description": "The last day of the next accounting period to be filed.",
+ "type": "string",
+ "format": "date"
+ },
+ "period_start_on": {
+ "description": "The first day of the next accounting period to be filed.",
+ "type": "string",
+ "format": "date"
+ }
+ }
+ },
+ "foreignCompanyDetails": {
+ "title": "foreignCompanyDetails",
+ "properties": {
+ "originating_registry": {
+ "description": "Company origin informations",
+ "type": "object",
+ "items": {
+ "$ref": "companyProfile.json#/definitions/originatingRegistry"
+ }
+ },
+ "registration_number": {
+ "description": "Registration number in company of incorporation.",
+ "type": "string"
+ },
+ "governed_by": {
+ "description": "Law governing the company in country of incorporation.",
+ "type": "string"
+ },
+ "company_type": {
+ "description": "Legal form of the company in the country of incorporation.",
+ "type": "string"
+ },
+ "is_a_credit_finance_institution": {
+ "description": "Is it a financial or credit institution.",
+ "type": "boolean"
+ },
+ "accounts": {
+ "description": "Foreign company account information.",
+ "items": {
+ "$ref": "companyProfile.json#/definitions/accountInformation"
+ },
+ "type": "object"
+ },
+ "business_activity": {
+ "description": "Type of business undertaken by the company.",
+ "type": "string"
+ },
+ "accounting_requirement": {
+ "description": "Accounts requirement.",
+ "items": {
+ "$ref": "companyProfile.json#/definitions/accountsRequired"
+ },
+ "type": "object"
+ }
+ }
+ },
+ "originatingRegistry": {
+ "title": "originatingRegistry",
+ "properties": {
+ "country": {
+ "description": "Country in which company was incorporated.",
+ "type": "string"
+ },
+ "name": {
+ "description": "Identity of register in country of incorporation.",
+ "type": "string"
+ }
+ }
+ },
+ "previousCompanyNames": {
+ "title": "previousCompanyNames",
+ "properties": {
+ "name": {
+ "description": "The previous company name",
+ "type": "string"
+ },
+ "effective_from": {
+ "description": "The date from which the company name was effective.",
+ "type": "string",
+ "format": "date"
+ },
+ "ceased_on": {
+ "description": "The date on which the company name ceased.",
+ "type": "string",
+ "format": "date"
+ }
+ },
+ "required": [
+ "name",
+ "effective_from",
+ "ceased_on"
+ ]
+ },
+ "corporateAnnotation": {
+ "title": "corporateAnnotation",
+ "properties": {
+ "created_on": {
+ "description": "The date on which the corporate annotation was created.",
+ "type": "string",
+ "format": "date"
+ },
+ "description": {
+ "description": "The details of a corporate annotation which has a corporate_annotation.type of “other”.",
+ "type": "string"
+ },
+ "type": {
+ "description": "The type of corporate annotation. \n For enumeration descriptions see `corporate_annotation_type` section in the [enumeration mappings] (https://github.com/companieshouse/api-enumerations/blob/master/constants.yml). ",
+ "type": "string"
+ }
+ },
+ "required": [
+ "created_on",
+ "type"
+ ]
+ },
+ "accountInformation": {
+ "title": "accountInformation",
+ "properties": {
+ "account_period_from:": {
+ "description": "Date account period starts under parent law.",
+ "items": {
+ "$ref": "companyProfile.json#/definitions/accountPeriodFrom"
+ },
+ "type": "object"
+ },
+ "account_period_to": {
+ "description": "Date account period ends under parent law.",
+ "items": {
+ "$ref": "companyProfile.json#/definitions/accountPeriodTo"
+ },
+ "type": "object"
+ },
+ "must_file_within": {
+ "description": "Time allowed from period end for disclosure of accounts under parent law.",
+ "items": {
+ "$ref": "companyProfile.json#/definitions/fileWithin"
+ },
+ "type": "object"
+ }
+ }
+ },
+ "accountPeriodFrom": {
+ "title": "accountPeriodFrom",
+ "properties": {
+ "day": {
+ "description": "Day on which accounting period starts under parent law.",
+ "type": "integer"
+ },
+ "month": {
+ "description": "Month in which accounting period starts under parent law.",
+ "type": "integer"
+ }
+ }
+ },
+ "accountPeriodTo": {
+ "title": "accountPeriodTo",
+ "properties": {
+ "day": {
+ "description": "Day on which accounting period ends under parent law.",
+ "type": "integer"
+ },
+ "month": {
+ "description": "Month in which accounting period ends under parent law.",
+ "type": "integer"
+ }
+ }
+ },
+ "fileWithin": {
+ "title": "fileWithin",
+ "properties": {
+ "months": {
+ "description": "Number of months within which to file.",
+ "type": "integer"
+ }
+ }
+ },
+ "accountsRequired": {
+ "title": "accountsRequired",
+ "properties": {
+ "foreign_account_type": {
+ "description": "Type of accounting requirement that applies. \n For enumeration descriptions see `foreign_account_type` section in the [enumeration mappings] (https://github.com/companieshouse/api-enumerations/blob/master/constants.yml). ",
+ "enum": [
+ "accounting-requirements-of-originating-country-apply",
+ "accounting-requirements-of-originating-country-do-not-apply"
+ ],
+ "type": "string"
+ },
+ "terms_of_account_publication": {
+ "description": "Describes how the publication date is derived. \n For enumeration descriptions see `terms_of_account_publication` section in the [enumeration mappings] (https://github.com/companieshouse/api-enumerations/blob/master/constants.yml). ",
+ "enum": [
+ "accounts-publication-date-supplied-by-company",
+ "accounting-publication-date-does-not-need-to-be-supplied-by-company",
+ "accounting-reference-date-allocated-by-companies-house"
+ ],
+ "type": "string"
+ }
+ }
+ },
+ "registeredOfficeAddress": {
+ "title": "registeredOfficeAddress",
+ "properties": {
+ "care_of": {
+ "description": "The care of name.",
+ "type": "string"
+ },
+ "address_line_1": {
+ "description": "The first line of the address.",
+ "type": "string"
+ },
+ "address_line_2": {
+ "description": "The second line of the address.",
+ "type": "string"
+ },
+ "country": {
+ "description": "The country.",
+ "enum": [
+ "Wales",
+ "England",
+ "Scotland",
+ "Great Britain",
+ "Not specified",
+ "United Kingdom",
+ "Northern Ireland"
+ ],
+ "type": "string"
+ },
+ "locality": {
+ "description": "The locality e.g London.",
+ "type": "string"
+ },
+ "po_box": {
+ "description": "The post-office box number.",
+ "type": "string"
+ },
+ "postal_code": {
+ "description": "The postal code e.g CF14 3UZ.",
+ "type": "string"
+ },
+ "premises": {
+ "description": "The property name or number.",
+ "type": "string"
+ },
+ "region": {
+ "description": "The region e.g Surrey.",
+ "type": "string"
+ }
+ }
+ },
+ "serviceAddress": {
+ "title": "serviceAddress",
+ "properties": {
+ "care_of": {
+ "description": "The care of name.",
+ "type": "string"
+ },
+ "address_line_1": {
+ "description": "The first line of the address.",
+ "type": "string"
+ },
+ "address_line_2": {
+ "description": "The second line of the address.",
+ "type": "string"
+ },
+ "country": {
+ "description": "The country e.g. United Kingdom.",
+ "type": "string"
+ },
+ "locality": {
+ "description": "The locality e.g London.",
+ "type": "string"
+ },
+ "po_box": {
+ "description": "The post-office box number.",
+ "type": "string"
+ },
+ "postal_code": {
+ "description": "The postal code e.g CF14 3UZ.",
+ "type": "string"
+ },
+ "region": {
+ "description": "The region e.g Surrey.",
+ "type": "string"
+ }
+ }
+ },
+ "branchCompanyDetails": {
+ "title": "branchCompanyDetails",
+ "properties": {
+ "business_activity": {
+ "description": "Type of business undertaken by the UK establishment.",
+ "type": "string"
+ },
+ "parent_company_number": {
+ "description": "Parent company number.",
+ "type": "string"
+ },
+ "parent_company_name": {
+ "description": "Parent company name.",
+ "type": "string"
+ }
+ }
+ },
+ "linksType": {
+ "title": "linksType",
+ "required": [
+ "self"
+ ],
+ "properties": {
+ "self": {
+ "description": "The URL of the resource.",
+ "type": "string"
+ },
+ "persons_with_significant_control": {
+ "description": "The URL of the persons with significant control list resource.",
+ "type": "string"
+ },
+ "persons_with_significant_control_statements": {
+ "description": "The URL of the persons with significant control statements list resource.",
+ "type": "string"
+ },
+ "registers": {
+ "description": "The URL of the registers resource for this company",
+ "type": "string"
+ },
+ "uk-establishments": {
+ "description": "The URL of the uk establishments list resource for this company.",
+ "type": "string"
+ },
+ "overseas": {
+ "description": "The URL of the overseas details resource for this company.",
+ "type": "string"
+ },
+ "officers": {
+ "description": "The URL of the company's officer list resource.",
+ "type": "string"
+ },
+ "insolvency": {
+ "description": "The URL of the company's insolvency list resource.",
+ "type": "string"
+ },
+ "filing_history": {
+ "description": "The URL of the company's filing history list resource.",
+ "type": "string"
+ },
+ "charges": {
+ "description": "The URL of the company's charges list resource.",
+ "type": "string"
+ },
+ "exemptions": {
+ "description": "The URL of the company's exemptions list resource.",
+ "type": "string"
+ }
+ }
+ }
+ }
+}
diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyRegisters.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyRegisters.json
new file mode 100644
index 0000000..4dc5759
--- /dev/null
+++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyRegisters.json
@@ -0,0 +1,454 @@
+{
+ "get": {
+ "summary": "Company registers",
+ "tags": [
+ "registers"
+ ],
+ "description": "Get the company registers information",
+ "parameters": [
+ {
+ "name": "company_number",
+ "in": "path",
+ "description": "The company number of the register information to return.",
+ "required": true,
+ "type": "string"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "readCompanyRegister",
+ "headers": {
+ "ETag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ }
+ },
+ "schema": {
+ "$ref": "companyRegisters.json#/definitions/companyRegister"
+ }
+ },
+ "401": {
+ "description": "Unauthorised"
+ }
+ }
+ },
+ "definitions": {
+ "companyRegister": {
+ "title": "companyRegister",
+ "type": "object",
+ "required": [
+ "links",
+ "company_number",
+ "kind",
+ "registers"
+ ],
+ "properties": {
+ "links": {
+ "type": "object",
+ "description": "A set of URLs related to the resource, including self.",
+ "items": {
+ "$ref": "companyRegisters.json#/definitions/linksType"
+ }
+ },
+ "company_number": {
+ "type": "string",
+ "description": "The number of the company."
+ },
+ "kind": {
+ "enum": [
+ "registers"
+ ],
+ "type": "string"
+ },
+ "registers": {
+ "description": "company registers information.",
+ "items": {
+ "$ref": "companyRegisters.json#/definitions/registers"
+ }
+ },
+ "etag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ }
+ }
+ },
+ "registers": {
+ "title": "registers",
+ "description": "Registered company information",
+ "type": "object",
+ "required": [
+ "directors",
+ "secretaries",
+ "persons_with_significant_control",
+ "usual_residential_address",
+ "members"
+ ],
+ "properties": {
+ "directors": {
+ "description": "List of registered company directors.",
+ "items": {
+ "$ref": "companyRegisters.json#/definitions/registerListDirectors"
+ }
+ },
+ "secretaries": {
+ "description": "List of registered company secretaries.",
+ "items": {
+ "$ref": "companyRegisters.json#/definitions/registerListSecretaries"
+ }
+ },
+ "persons_with_significant_control": {
+ "description": "List of registered company persons with significant control.",
+ "items": {
+ "$ref": "companyRegisters.json#/definitions/registerListPersonsWithSignificantControl"
+ }
+ },
+ "usual_residential_address": {
+ "description": "List of register addresses.",
+ "items": {
+ "$ref": "companyRegisters.json#/definitions/registerListUsualResidentialAddress"
+ }
+ },
+ "llp_usual_residential_address": {
+ "description": "List of register addresses.",
+ "items": {
+ "$ref": "companyRegisters.json#/definitions/registerListLLPUsualResidentialAddress"
+ }
+ },
+ "members": {
+ "description": "List of registered company members..",
+ "items": {
+ "$ref": "companyRegisters.json#/definitions/registerListMembers"
+ }
+ },
+ "llp_members": {
+ "description": "List of registered llp members.",
+ "items": {
+ "$ref": "companyRegisters.json#/definitions/registerListLLPMembers"
+ }
+ }
+ }
+ },
+ "registerListDirectors": {
+ "title": "registerListDirectors",
+ "required": [
+ "register_type",
+ "items"
+ ],
+ "properties": {
+ "register_type": {
+ "description": "The register type.",
+ "enum": [
+ "directors"
+ ],
+ "type": "string"
+ },
+ "items": {
+ "items": {
+ "$ref": "companyRegisters.json#/definitions/registeredItems"
+ },
+ "type": "array"
+ },
+ "links": {
+ "description": "A set of URLs related to the resource.",
+ "type": "object",
+ "items": {
+ "$ref": "companyRegisters.json#/definitions/linksDirectorsRegister"
+ }
+ }
+ }
+ },
+ "registerListSecretaries": {
+ "title": "registerListSecretaries",
+ "required": [
+ "register_type",
+ "items"
+ ],
+ "properties": {
+ "register_type": {
+ "description": "The register type.",
+ "enum": [
+ "secretaries"
+ ],
+ "type": "string"
+ },
+ "items": {
+ "items": {
+ "$ref": "companyRegisters.json#/definitions/registeredItems"
+ },
+ "type": "array"
+ },
+ "links": {
+ "description": "A set of URLs related to the resource.",
+ "type": "object",
+ "items": {
+ "$ref": "companyRegisters.json#/definitions/linksSecretaryRegister"
+ }
+ }
+ }
+ },
+ "registerListPersonsWithSignificantControl": {
+ "title": "registerListPersonsWithSignificantControl",
+ "required": [
+ "register_type",
+ "items"
+ ],
+ "properties": {
+ "register_type": {
+ "description": "The register type.",
+ "enum": [
+ "persons-with-significant-control"
+ ],
+ "type": "string"
+ },
+ "items": {
+ "items": {
+ "$ref": "companyRegisters.json#/definitions/registeredItems"
+ },
+ "type": "array"
+ },
+ "links": {
+ "description": "A set of URLs related to the resource.",
+ "type": "object",
+ "items": {
+ "$ref": "companyRegisters.json#/definitions/linksPersonsWithSignificantControlRegister"
+ }
+ }
+ }
+ },
+ "registerListUsualResidentialAddress": {
+ "title": "registerListUsualResidentialAddress",
+ "required": [
+ "register_type",
+ "items"
+ ],
+ "properties": {
+ "register_type": {
+ "description": "The register type.",
+ "enum": [
+ "usual-residential-address"
+ ],
+ "type": "string"
+ },
+ "items": {
+ "items": {
+ "$ref": "companyRegisters.json#/definitions/registeredItems"
+ },
+ "type": "array"
+ },
+ "links": {
+ "description": "A set of URLs related to the resource.",
+ "type": "object",
+ "items": {
+ "$ref": "companyRegisters.json#/definitions/linksListUsualResidentialAddress"
+ }
+ }
+ }
+ },
+ "registerListLLPUsualResidentialAddress": {
+ "title": "registerListLLPUsualResidentialAddress",
+ "properties": {
+ "register_type": {
+ "description": "The register type.",
+ "enum": [
+ "llp-usual-residential-address"
+ ],
+ "type": "string"
+ },
+ "items": {
+ "items": {
+ "$ref": "companyRegisters.json#/definitions/registeredItems"
+ },
+ "type": "array"
+ },
+ "links": {
+ "description": "A set of URLs related to the resource.",
+ "items": {
+ "$ref": "companyRegisters.json#/definitions/linksListLLPUsualResidentialAddress"
+ },
+ "type": "object"
+ }
+ },
+ "required": [
+ "register_type",
+ "items"
+ ]
+ },
+ "registerListMembers": {
+ "title": "registerListMembers",
+ "required": [
+ "register_type",
+ "items"
+ ],
+ "properties": {
+ "register_type": {
+ "description": "The register type.",
+ "enum": [
+ "members"
+ ],
+ "type": "string"
+ },
+ "items": {
+ "items": {
+ "$ref": "companyRegisters.json#/definitions/registeredItems"
+ },
+ "type": "array"
+ },
+ "links": {
+ "description": "A set of URLs related to the resource.",
+ "type": "object",
+ "items": {
+ "$ref": "companyRegisters.json#/definitions/linksListMembers"
+ }
+ }
+ }
+ },
+ "registerListLLPMembers": {
+ "title": "registerListLLPMembers",
+ "properties": {
+ "register_type": {
+ "description": "The register type.",
+ "enum": [
+ "llp_members"
+ ],
+ "type": "string"
+ },
+ "items": {
+ "items": {
+ "$ref": "companyRegisters.json#/definitions/registeredItems"
+ },
+ "type": "array"
+ },
+ "links": {
+ "description": "A set of URLs related to the resource.",
+ "items": {
+ "$ref": "companyRegisters.json#/definitions/linksListLLPMembers"
+ },
+ "type": "object"
+ }
+ },
+ "required": [
+ "register_type",
+ "items"
+ ]
+ },
+ "registeredItems": {
+ "title": "registeredItems",
+ "required": [
+ "register_moved_to",
+ "moved_on",
+ "links"
+ ],
+ "properties": {
+ "moved_on": {
+ "description": "The date registered on",
+ "type": "string",
+ "format": "date"
+ },
+ "register_moved_to": {
+ "description": "Location of registration",
+ "type": "string",
+ "enum": [
+ "public-register",
+ "registered-office",
+ "single-alternative-inspection-location",
+ "unspecified-location"
+ ]
+ },
+ "links": {
+ "description": "A set of URLs related to the resource.",
+ "items": {
+ "$ref": "companyRegisters.json#/definitions/linksItems"
+ },
+ "type": "object"
+ }
+ }
+ },
+ "linksType": {
+ "title": "linksType",
+ "required": [
+ "self"
+ ],
+ "properties": {
+ "self": {
+ "description": "The URL of the resource.",
+ "type": "string"
+ }
+ }
+ },
+ "linksItems": {
+ "title": "linksItems",
+ "required": [
+ "filing"
+ ],
+ "properties": {
+ "filing": {
+ "description": "The URL of the transaction for the resource.",
+ "type": "string"
+ }
+ }
+ },
+ "linksDirectorsRegister": {
+ "title": "linksDirectorsRegister",
+ "properties": {
+ "directors_register": {
+ "description": "The URL for the resource.",
+ "type": "string"
+ }
+ }
+ },
+ "linksSecretaryRegister": {
+ "title": "linksSecretaryRegister",
+ "properties": {
+ "secretaries_register": {
+ "description": "The URL for the resource.",
+ "type": "string"
+ }
+ }
+ },
+ "linksPersonsWithSignificantControlRegister": {
+ "title": "linksPersonsWithSignificantControlRegister",
+ "properties": {
+ "persons_with_significant_control_register": {
+ "description": "The URL for the resource.",
+ "type": "string"
+ }
+ }
+ },
+ "linksListUsualResidentialAddress": {
+ "title": "linksListUsualResidentialAddress",
+ "properties": {
+ "usual_residential_address": {
+ "description": "The URL for the resource.",
+ "type": "string"
+ }
+ }
+ },
+ "linksListLLPUsualResidentialAddress": {
+ "title": "linksListLLPUsualResidentialAddress",
+ "properties": {
+ "llp_usual_residential_address": {
+ "description": "The URL for the resource.",
+ "type": "string"
+ }
+ }
+ },
+ "linksListMembers": {
+ "title": "linksListMembers",
+ "properties": {
+ "members": {
+ "description": "The URL for the resource.",
+ "type": "string"
+ }
+ }
+ },
+ "linksListLLPMembers": {
+ "title": "linksListLLPMembers",
+ "properties": {
+ "llp_members": {
+ "description": "The URL for the resource.",
+ "type": "string"
+ }
+ }
+ }
+ }
+}
diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyUKEstablishments.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyUKEstablishments.json
new file mode 100644
index 0000000..272f2de
--- /dev/null
+++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyUKEstablishments.json
@@ -0,0 +1,125 @@
+{
+ "get": {
+ "summary": "Company UK Establishments",
+ "description": "List of uk-establishments companies",
+ "parameters": [
+ {
+ "name": "company_number",
+ "in": "path",
+ "description": "Company number",
+ "required": true,
+ "type": "string"
+ }
+ ],
+ "tags": [
+ "UKEstablishments"
+ ],
+ "responses": {
+ "200": {
+ "description": "Resource returned",
+ "schema": {
+ "$ref": "companyUKEstablishments.json#/definitions/companyUKEstablishments"
+ }
+ },
+ "401": {
+ "description": "Unauthorised"
+ }
+ }
+ },
+ "definitions": {
+ "companyUKEstablishments": {
+ "title": "companyUKEstablishments",
+ "required": [
+ "etag",
+ "kind",
+ "items"
+ ],
+ "properties": {
+ "etag": {
+ "type": "string",
+ "description": "The ETag of the resource."
+ },
+ "kind": {
+ "type": "string",
+ "description": "UK Establishment companies.",
+ "enum": [
+ "ukestablishment-companies"
+ ]
+ },
+ "links": {
+ "type": "object",
+ "description": "UK Establishment Resources related to this company.",
+ "items": {
+ "$ref": "companyUKEstablishments.json#/definitions/self_links"
+ }
+ },
+ "items": {
+ "type": "array",
+ "description": "List of UK Establishment companies.",
+ "items": {
+ "$ref": "companyUKEstablishments.json#/definitions/companyDetails"
+ }
+ }
+ }
+ },
+ "companyDetails": {
+ "title": "companyDetails",
+ "required": [
+ "company_number",
+ "company_name",
+ "company_status",
+ "links"
+ ],
+ "properties": {
+ "company_number": {
+ "type": "string",
+ "description": "The number of the company."
+ },
+ "company_name": {
+ "type": "string",
+ "description": "The name of the company."
+ },
+ "company_status": {
+ "type": "string",
+ "description": "Company status."
+ },
+ "locality": {
+ "type": "string",
+ "description": "The locality e.g London."
+ },
+ "links": {
+ "description": "Resources related to this company.",
+ "type": "object",
+ "items": {
+ "$ref": "companyUKEstablishments.json#/definitions/links"
+ }
+ }
+ }
+ },
+ "self_links": {
+ "title": "self_links",
+ "required": [
+ "self"
+ ],
+ "properties": {
+ "self": {
+ "type": "string",
+ "description": "Link to this company."
+ }
+ }
+ },
+ "links": {
+ "title": "links",
+ "required": [
+ "company"
+ ],
+ "properties": {
+ "company": {
+ "type": "string",
+ "description": "The link to the company."
+ }
+ }
+ }
+ }
+}
+
diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/disqualifications.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/disqualifications.json
new file mode 100644
index 0000000..569d99e
--- /dev/null
+++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/disqualifications.json
@@ -0,0 +1,408 @@
+{
+ "getNatural": {
+ "get": {
+ "summary": "Get natural officers disqualifications",
+ "description": "Get a natural officer's disqualifications",
+ "x-operationName": "get natural officer",
+ "tags": [
+ "officerDisqualifications"
+ ],
+ "parameters": [
+ {
+ "name": "officer_id",
+ "description": "The disqualified officer's id.",
+ "in": "path",
+ "required": true,
+ "type": "string"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Natural officer's disqualifications returned",
+ "schema": {
+ "$ref": "disqualifications.json#/definitions/naturalDisqualification"
+ },
+ "headers": {
+ "ETag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorised"
+ },
+ "404": {
+ "description": "Resource not found"
+ }
+ }
+ }
+ },
+ "getCorporate": {
+ "get": {
+ "summary": "Get a corporate officers disqualifications",
+ "description": "Get a corporate officer's disqualifications",
+ "x-operationName": "get corporate officer",
+ "tags": [
+ "officerDisqualifications"
+ ],
+ "parameters": [
+ {
+ "description": "The disqualified officer id.",
+ "name": "officer_id",
+ "in": "path",
+ "required": true,
+ "type": "string"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Corporate officer's disqualifications returned",
+ "schema": {
+ "$ref": "disqualifications.json#/definitions/corporateDisqualification"
+ },
+ "headers": {
+ "ETag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ }
+ }
+ },
+ "401": {
+ "description": "Unauthorised"
+ },
+ "404": {
+ "description": "Resource not found"
+ }
+ }
+ }
+ },
+ "definitions": {
+ "corporateDisqualification": {
+ "title": "corporateDisqualification",
+ "required": [
+ "disqualifications",
+ "etag",
+ "kind",
+ "links",
+ "name"
+ ],
+ "properties": {
+ "company_number": {
+ "description": "The registration number of the disqualified officer.",
+ "type": "string"
+ },
+ "country_of_registration": {
+ "description": "The country in which the disqualified officer was registered.",
+ "type": "string"
+ },
+ "etag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ },
+ "kind": {
+ "type": "string",
+ "enum": [
+ "corporate-disqualification"
+ ]
+ },
+ "name": {
+ "description": "The name of the disqualified officer.",
+ "type": "string"
+ },
+ "links": {
+ "description": "Links to other resources associated with this officer disqualification resource.",
+ "type": "object",
+ "items": {
+ "$ref": "disqualifications.json#/definitions/links"
+ }
+ },
+ "disqualifications": {
+ "description": "The officer's disqualifications.",
+ "type": "array",
+ "items": {
+ "$ref": "disqualifications.json#/definitions/disqualification"
+ }
+ },
+ "permissions_to_act": {
+ "description": "Permissions that the disqualified officer has to act outside of their disqualification.",
+ "type": "array",
+ "items": {
+ "$ref": "disqualifications.json#/definitions/permission_to_act"
+ }
+ },
+ "person_number": {
+ "description": "The disqualified officer's person number.",
+ "type": "string"
+ }
+ }
+ },
+ "naturalDisqualification": {
+ "title": "naturalDisqualification",
+ "required": [
+ "disqualifications",
+ "etag",
+ "kind",
+ "links",
+ "surname"
+ ],
+ "properties": {
+ "date_of_birth": {
+ "description": "The disqualified officer's date of birth.",
+ "type": "string",
+ "format": "date"
+ },
+ "etag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ },
+ "forename": {
+ "description": "The forename of the disqualified officer.",
+ "type": "string"
+ },
+ "honours": {
+ "description": "The honours that the disqualified officer has.",
+ "type": "string"
+ },
+ "kind": {
+ "type": "string",
+ "enum": [
+ "natural-disqualification"
+ ]
+ },
+ "nationality": {
+ "description": "The nationality of the disqualified officer.",
+ "type": "string"
+ },
+ "other_forenames": {
+ "description": "The other forenames of the disqualified officer.",
+ "type": "string"
+ },
+ "surname": {
+ "description": "The surname of the disqualified officer.",
+ "type": "string"
+ },
+ "title": {
+ "description": "The title of the disqualified officer.",
+ "type": "string"
+ },
+ "links": {
+ "description": "Links to other resources associated with this officer disqualification resource.",
+ "type": "object",
+ "items": {
+ "$ref": "disqualifications.json#/definitions/links"
+ }
+ },
+ "disqualifications": {
+ "description": "The officer's disqualifications.",
+ "type": "array",
+ "items": {
+ "$ref": "disqualifications.json#/definitions/disqualification"
+ }
+ },
+ "permissions_to_act": {
+ "description": "Permissions to act that have been granted for the disqualified officer.",
+ "type": "array",
+ "items": {
+ "$ref": "disqualifications.json#/definitions/permission_to_act"
+ }
+ },
+ "person_number": {
+ "description": "The disqualified officer's person number.",
+ "type": "string"
+ }
+ }
+ },
+ "address": {
+ "title": "address",
+ "properties": {
+ "address_line_1": {
+ "description": "The first line of the address.",
+ "type": "string"
+ },
+ "address_line_2": {
+ "description": "The second line of the address.",
+ "type": "string"
+ },
+ "country": {
+ "description": "The country. For example, UK.",
+ "type": "string"
+ },
+ "locality": {
+ "description": "The locality. For example London.",
+ "type": "string"
+ },
+ "postal_code": {
+ "description": "The postal code. For example CF14 3UZ.",
+ "type": "string"
+ },
+ "premises": {
+ "description": "The property name or number.",
+ "type": "string"
+ },
+ "region": {
+ "description": "The region. For example Surrey.",
+ "type": "string"
+ }
+ }
+ },
+ "links": {
+ "title": "links",
+ "properties": {
+ "self": {
+ "description": "Link to this disqualification resource.",
+ "type": "string"
+ }
+ },
+ "required": [
+ "self"
+ ]
+ },
+ "disqualification": {
+ "title": "disqualification",
+ "properties": {
+ "case_identifier": {
+ "description": "The case identifier of the disqualification.",
+ "type": "string"
+ },
+ "address": {
+ "description": "The address of the disqualified officer as provided by the disqualifying authority.",
+ "type": "object",
+ "items": {
+ "$ref": "disqualifications.json#/definitions/address"
+ }
+ },
+ "company_names": {
+ "description": "The companies in which the misconduct took place.",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "court_name": {
+ "description": "The name of the court that handled the disqualification case.",
+ "type": "string"
+ },
+ "disqualification_type": {
+ "description": "An enumeration type that provides the disqualifying authority that handled the disqualification case.\n For enumeration descriptions see `disqualification_type` section in the [enumeration mappings] (https://github.com/companieshouse/api-enumerations/blob/master/disqualified_officer_descriptions.yml)",
+ "type": "string"
+ },
+ "disqualified_from": {
+ "description": "The date that the disqualification starts.",
+ "type": "string",
+ "format": "date"
+ },
+ "disqualified_until": {
+ "description": "The date that the disqualification ends.",
+ "type": "string",
+ "format": "date"
+ },
+ "heard_on": {
+ "description": "The date the disqualification hearing was on.",
+ "type": "string",
+ "format": "date"
+ },
+ "undertaken_on": {
+ "description": "The date the disqualification undertaking was agreed on.",
+ "type": "string",
+ "format": "date"
+ },
+ "last_variation": {
+ "description": "The latest variation made to the disqualification.",
+ "type": "array",
+ "items": {
+ "$ref": "disqualifications.json#/definitions/last_variation"
+ }
+ },
+ "reason": {
+ "description": "The reason for the disqualification.",
+ "type": "object",
+ "items": {
+ "$ref": "disqualifications.json#/definitions/reason"
+ }
+ }
+ },
+ "required": [
+ "address",
+ "disqualification_type",
+ "disqualified_from",
+ "disqualified_until",
+ "reason"
+ ]
+ },
+ "last_variation": {
+ "title": "last_variation",
+ "properties": {
+ "varied_on": {
+ "description": "The date the variation was made against the disqualification.",
+ "type": "string",
+ "format": "date"
+ },
+ "case_identifier": {
+ "description": "The case identifier of the variation.",
+ "type": "string"
+ },
+ "court_name": {
+ "description": "The name of the court that handled the variation case.",
+ "type": "string"
+ }
+ }
+ },
+ "permission_to_act": {
+ "title": "permission_to_act",
+ "required": [
+ "expires_on",
+ "granted_on"
+ ],
+ "properties": {
+ "company_names": {
+ "description": "The companies for which the disqualified officer has permission to act.",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "court_name": {
+ "description": "The name of the court that granted the permission to act.",
+ "type": "string"
+ },
+ "expires_on": {
+ "description": "The date that the permission ends.",
+ "type": "string",
+ "format": "date"
+ },
+ "granted_on": {
+ "description": "The date that the permission starts.",
+ "type": "string",
+ "format": "date"
+ }
+ }
+ },
+ "reason": {
+ "title": "reason",
+ "properties": {
+ "description_identifier": {
+ "description": "An enumeration type that provides the description for the reason of disqualification.\n For enumeration descriptions see `description_identifier` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/disqualified_officer_descriptions.yml)",
+ "type": "string"
+ },
+ "act": {
+ "description": "An enumeration type that provides the law under which the disqualification was made.\n For enumeration descriptions see `act` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/disqualified_officer_descriptions.yml)",
+ "type": "string"
+ },
+ "article": {
+ "description": "The article of the act under which the disqualification was made.\n Only applicable if `reason.act` is `company-directors-disqualification-northern-ireland-order-2002`.",
+ "type": "string"
+ },
+ "section": {
+ "description": "The section of the act under which the disqualification was made.\n Only applicable if `reason.act` is `company-directors-disqualification-act-1986` or `sanctions-anti-money-laundering-act-2018` or `sanctions-counter-terrorism-regulations-2019`.",
+ "type": "string"
+ }
+ },
+ "required": [
+ "description_identifier",
+ "act"
+ ]
+ }
+ }
+}
+
diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/errorModel.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/errorModel.json
new file mode 100644
index 0000000..b0b16bf
--- /dev/null
+++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/errorModel.json
@@ -0,0 +1,68 @@
+{
+ "definitions": {
+ "error": {
+ "title": "error",
+ "required": [
+ "errors"
+ ],
+ "properties": {
+ "errors": {
+ "type": "array",
+ "description": "A list of errors found",
+ "items": {
+ "$ref": "errorModel.json#/definitions/errorDetail"
+ }
+ }
+ }
+ },
+ "errorDetail": {
+ "title": "errorDetail",
+ "required": [
+ "type",
+ "error"
+ ],
+ "properties": {
+ "type": {
+ "type": "string",
+ "description": "Type of error",
+ "enum": [
+ "ch:service",
+ "ch:validation"
+ ]
+ },
+ "location_type": {
+ "type": "string",
+ "description": "Describes the type of location returned so that it may be parsed appropriately",
+ "enum": [
+ "json-path",
+ "query-parameter"
+ ]
+ },
+ "location": {
+ "type": "string",
+ "description": "The location in the submitted request in which the error relates. This parameter is only provided when errors[].type is set to \"ch:validation\"."
+ },
+ "error": {
+ "type": "string",
+ "description": "The error being returned. See github for valid [enumeration types](https://github.com/companieshouse/api-enumerations/blob/develop/errors.yml)"
+ },
+ "error_values": {
+ "type": "array",
+ "description": "A collection of argument name and value pairs which, when substituted into the error string, provide the full description of the error. As many name/value pairs as necessary to complete the error description are returned. See example above.",
+ "items": {
+ "$ref": "errorModel.json#/definitions/error_values"
+ }
+ }
+ }
+ },
+ "error_values": {
+ "title": "error_values",
+ "properties": {
+ "": {
+ "type": "string",
+ "description": "The element name and value pair required to complete the error description, will repeat as necessary."
+ }
+ }
+ }
+ }
+}
diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/exemptions.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/exemptions.json
new file mode 100644
index 0000000..1437855
--- /dev/null
+++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/exemptions.json
@@ -0,0 +1,241 @@
+{
+ "get": {
+ "description": "Company exemptions information",
+ "parameters": [
+ {
+ "name": "company_number",
+ "in": "path",
+ "description": "The company number that the exemptions list is required for.",
+ "required": true,
+ "type": "string"
+ }
+ ],
+ "tags": [
+ "exemptions"
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful response",
+ "schema": {
+ "$ref": "exemptions.json#/definitions/companyExemptions"
+ }
+ },
+ "401": {
+ "description": "Unauthorised"
+ },
+ "404": {
+ "description": "Resource not found"
+ }
+ }
+ },
+ "definitions": {
+ "companyExemptions": {
+ "title": "companyExemptions",
+ "required": [
+ "links",
+ "kind",
+ "etag",
+ "exemptions"
+ ],
+ "properties": {
+ "links": {
+ "description": "A set of URLs related to the resource, including self.",
+ "items": {
+ "$ref": "exemptions.json#/definitions/linksType"
+ },
+ "type": "array"
+ },
+ "kind": {
+ "type": "string",
+ "enum": [
+ "exemptions"
+ ]
+ },
+ "etag": {
+ "type": "string",
+ "description": "The ETag of the resource."
+ },
+ "exemptions": {
+ "description": "Company exemptions information.",
+ "items": {
+ "$ref": "exemptions.json#/definitions/exemptions"
+ }
+ }
+ }
+ },
+ "exemptions": {
+ "description": "Exemptions information.",
+ "properties": {
+ "psc_exempt_as_trading_on_regulated_market": {
+ "description": "If present the company has been or is exempt from keeping a PSC register, as it has voting shares admitted to trading on a regulated market other than the UK.",
+ "items": {
+ "$ref": "exemptions.json#/definitions/pscExemptAsTradingOnRegulatedMarketItem"
+ }
+ },
+ "psc_exempt_as_shares_admitted_on_market": {
+ "description": "If present the company has been or is exempt from keeping a PSC register, as it has voting shares admitted to trading on a market listed in the Register of People with Significant Control Regulations 2016.",
+ "items": {
+ "$ref": "exemptions.json#/definitions/pscExemptAsSharesAdmittedOnMarketItem"
+ }
+ },
+ "psc_exempt_as_trading_on_uk_regulated_market": {
+ "description": "If present the company has been or is exempt from keeping a PSC register, as it has voting shares admitted to trading on a UK regulated market.",
+ "items": {
+ "$ref": "exemptions.json#/definitions/pscExemptAsTradingOnUkRegualatedMarketItem"
+ }
+ },
+ "psc_exempt_as_trading_on_eu_regulated_market": {
+ "description": "If present the company has been or is exempt from keeping a PSC register, as it has voting shares admitted to trading on an EU regulated market.",
+ "items": {
+ "$ref": "exemptions.json#/definitions/pscExemptAsTradingOnEuRegualatedMarketItem"
+ }
+ },
+ "disclosure_transparency_rules_chapter_five_applies": {
+ "description": "If present the company has been or is exempt from keeping a PSC register, because it is a DTR issuer and the shares are admitted to trading on a regulated market.",
+ "items": {
+ "$ref": "exemptions.json#/definitions/diclosureTransparencyRulesChapterFiveAppliesItem"
+ }
+ }
+ }
+ },
+ "pscExemptAsTradingOnRegulatedMarketItem": {
+ "properties": {
+ "items": {
+ "type": "array",
+ "description": "List of dates",
+ "items": {
+ "$ref": "exemptions.json#/definitions/exemptionItem"
+ }
+ },
+ "exemption_type": {
+ "description": "The exemption type.",
+ "type": "string",
+ "enum": [
+ "psc-exempt-as-trading-on-regulated-market"
+ ]
+ }
+ },
+ "required": [
+ "exemption_type",
+ "items"
+ ]
+ },
+ "pscExemptAsSharesAdmittedOnMarketItem": {
+ "required": [
+ "exemption_type",
+ "items"
+ ],
+ "properties": {
+ "items": {
+ "description": "List of dates",
+ "type": "array",
+ "items": {
+ "$ref": "exemptions.json#/definitions/exemptionItem"
+ }
+ },
+ "exemption_type": {
+ "description": "The exemption type.",
+ "enum": [
+ "psc-exempt-as-shares-admitted-on-market"
+ ],
+ "type": "string"
+ }
+ }
+ },
+ "pscExemptAsTradingOnUkRegualatedMarketItem": {
+ "required": [
+ "exemption_type",
+ "items"
+ ],
+ "properties": {
+ "items": {
+ "description": "List of dates",
+ "type": "array",
+ "items": {
+ "$ref": "exemptions.json#/definitions/exemptionItem"
+ }
+ },
+ "exemption_type": {
+ "description": "The exemption type.",
+ "enum": [
+ "psc-exempt-as-trading-on-uk-regulated-market"
+ ],
+ "type": "string"
+ }
+ }
+ },
+ "pscExemptAsTradingOnEuRegualatedMarketItem": {
+ "required": [
+ "exemption_type",
+ "items"
+ ],
+ "properties": {
+ "items": {
+ "description": "List of dates",
+ "type": "array",
+ "items": {
+ "$ref": "exemptions.json#/definitions/exemptionItem"
+ }
+ },
+ "exemption_type": {
+ "description": "The exemption type.",
+ "enum": [
+ "psc-exempt-as-trading-on-eu-regulated-market"
+ ],
+ "type": "string"
+ }
+ }
+ },
+ "diclosureTransparencyRulesChapterFiveAppliesItem": {
+ "properties": {
+ "items": {
+ "description": "List of exemption periods.",
+ "items": {
+ "$ref": "exemptions.json#/definitions/exemptionItem"
+ },
+ "type": "array"
+ },
+ "exemption_type": {
+ "description": "The exemption type.",
+ "enum": [
+ "disclosure-transparency-rules-chapter-five-applies"
+ ],
+ "type": "string"
+ }
+ },
+ "required": [
+ "exemption_type",
+ "items"
+ ]
+ },
+ "exemptionItem": {
+ "properties": {
+ "exempt_from": {
+ "description": "Exemption valid from.",
+ "type": "string",
+ "format": "date"
+ },
+ "exempt_to": {
+ "description": "Exemption valid to.",
+ "type": "string",
+ "format": "date"
+ }
+ },
+ "required": [
+ "exempt_from"
+ ]
+ },
+ "linksType": {
+ "properties": {
+ "self": {
+ "description": "The URL of this resource.",
+ "type": "string"
+ }
+ },
+ "required": [
+ "self"
+ ]
+ }
+ }
+}
+
diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/filingHistory.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/filingHistory.json
new file mode 100644
index 0000000..65539f7
--- /dev/null
+++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/filingHistory.json
@@ -0,0 +1,348 @@
+{
+ "getFilingHistory": {
+ "get": {
+ "summary": "filingHistoryItem resource",
+ "description": "Get the filing history item of a company",
+ "tags": [
+ "filingHistory"
+ ],
+ "parameters": [
+ {
+ "name": "company_number",
+ "in": "path",
+ "description": "The company number that the single filing is required for.",
+ "required": true,
+ "type": "string"
+ },
+ {
+ "name": "transaction_id",
+ "in": "path",
+ "description": "The transaction id that the filing history is required for.",
+ "required": true,
+ "type": "string"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Filing history items resource returned",
+ "schema": {
+ "$ref": "filingHistory.json#/definitions/filingHistoryItem"
+ }
+ },
+ "401": {
+ "description": "Unauthorised",
+ "schema": {
+ "$ref": "errorModel.json#/definitions/error"
+ }
+ },
+ "404": {
+ "description": "Filing history not available for this company"
+ }
+ }
+ }
+ },
+ "listFilingHistory": {
+ "get": {
+ "summary": "filingHistoryList resource",
+ "description": "Get the filing history list of a company",
+ "x-operationName": "list",
+ "tags": [
+ "filingHistory"
+ ],
+ "parameters": [
+ {
+ "name": "category",
+ "in": "query",
+ "description": "One or more comma-separated categories to filter by (inclusive).",
+ "required": false,
+ "type": "string"
+ },
+ {
+ "name": "company_number",
+ "in": "path",
+ "description": "The company number that the filing history is required for.",
+ "required": true,
+ "type": "string"
+ },
+ {
+ "name": "items_per_page",
+ "in": "query",
+ "description": "The number of filing history items to return per page.",
+ "required": false,
+ "type": "integer"
+ },
+ {
+ "name": "start_index",
+ "in": "query",
+ "description": "The index into the entire result set that this result page starts.",
+ "required": false,
+ "type": "integer"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Filing history items resource returned",
+ "schema": {
+ "$ref": "filingHistory.json#/definitions/filingHistoryList"
+ }
+ },
+ "401": {
+ "description": "Unauthorised",
+ "schema": {
+ "$ref": "errorModel.json#/definitions/error"
+ }
+ },
+ "404": {
+ "description": "Filing history not available for this company"
+ }
+ }
+ }
+ },
+ "definitions": {
+ "annotation": {
+ "title": "annotation",
+ "required": [
+ "date",
+ "description"
+ ],
+ "properties": {
+ "annotation": {
+ "description": "The annotation text.",
+ "type": "string"
+ },
+ "date": {
+ "type": "string",
+ "format": "date",
+ "description": "The date the annotation was added."
+ },
+ "description": {
+ "type": "string",
+ "description": "A description of the annotation.\n For enumeration descriptions see `description` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/filing_history_descriptions.yml) file."
+ }
+ }
+ },
+ "associatedFiling": {
+ "title": "associatedFiling",
+ "required": [
+ "date",
+ "description",
+ "type"
+ ],
+ "properties": {
+ "date": {
+ "description": "The date the associated filing was processed.",
+ "type": "string",
+ "format": "date"
+ },
+ "description": {
+ "type": "string",
+ "description": "A description of the associated filing.\n For enumeration descriptions see `description` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/filing_history_descriptions.yml) file."
+ },
+ "type": {
+ "description": "The type of the associated filing.",
+ "type": "string"
+ }
+ }
+ },
+ "filingHistoryItem": {
+ "title": "filingHistoryItem",
+ "required": [
+ "category",
+ "date",
+ "description",
+ "type",
+ "transaction_id"
+ ],
+ "properties": {
+ "annotations": {
+ "description": "Annotations for the filing",
+ "items": {
+ "$ref": "filingHistory.json#/definitions/annotation"
+ },
+ "type": "array"
+ },
+ "associated_filings": {
+ "description": "Any filings associated with the current item",
+ "items": {
+ "$ref": "filingHistory.json#/definitions/associatedFiling"
+ },
+ "type": "array"
+ },
+ "barcode": {
+ "description": "The barcode of the document.",
+ "type": "string"
+ },
+ "transaction_id": {
+ "description": "The transaction ID of the filing.",
+ "type": "string"
+ },
+ "category": {
+ "description": "The category of the document filed.",
+ "enum": [
+ "accounts",
+ "address",
+ "annual-return",
+ "capital",
+ "change-of-name",
+ "incorporation",
+ "liquidation",
+ "miscellaneous",
+ "mortgage",
+ "officers",
+ "resolution"
+ ],
+ "type": "string"
+ },
+ "date": {
+ "description": "The date the filing was processed.",
+ "type": "string",
+ "format": "date"
+ },
+ "description": {
+ "description": "A description of the filing.\n For enumeration descriptions see `description` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/filing_history_descriptions.yml) file.",
+ "type": "string"
+ },
+ "links": {
+ "description": "Links to other resources associated with this filing history item.",
+ "type": "object",
+ "items": {
+ "$ref": "filingHistory.json#/definitions/filingHistoryItemLinks"
+ }
+ },
+ "pages": {
+ "description": "Number of pages within the PDF document (links.document_metadata)",
+ "type": "integer"
+ },
+ "paper_filed": {
+ "description": "If true, indicates this is a paper filing.",
+ "type": "boolean"
+ },
+ "resolutions": {
+ "description": "Resolutions for the filing",
+ "items": {
+ "$ref": "filingHistory.json#/definitions/resolution"
+ },
+ "type": "array"
+ },
+ "subcategory": {
+ "description": "The sub-category of the document filed.",
+ "enum": [
+ "resolution"
+ ],
+ "type": "string"
+ },
+ "type": {
+ "description": "The type of filing.",
+ "type": "string"
+ }
+ }
+ },
+ "filingHistoryItemLinks": {
+ "title": "filingHistoryItemLinks",
+ "properties": {
+ "self": {
+ "description": "Link to this filing history item.",
+ "type": "string"
+ },
+ "document_metadata": {
+ "description": "Link to the document metadata associated with this filing history item. See the Document API documentation for more details.",
+ "type": "string"
+ }
+ }
+ },
+ "filingHistoryList": {
+ "title": "filingHistoryList",
+ "required": [
+ "etag",
+ "items",
+ "items_per_page",
+ "kind",
+ "start_index",
+ "total_count"
+ ],
+ "properties": {
+ "filing_history_status": {
+ "description": "The status of this filing history.",
+ "type": "string",
+ "enum": [
+ "filing-history-available"
+ ]
+ },
+ "etag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ },
+ "items": {
+ "description": "The filing history items.",
+ "items": {
+ "$ref": "filingHistory.json#/definitions/filingHistoryItem"
+ },
+ "type": "array"
+ },
+ "items_per_page": {
+ "description": "The number of filing history items returned per page.",
+ "type": "integer"
+ },
+ "kind": {
+ "description": "Indicates this resource is a filing history.",
+ "enum": [
+ "filing-history"
+ ],
+ "type": "string"
+ },
+ "start_index": {
+ "description": "The index into the entire result set that this result page starts.",
+ "type": "integer"
+ },
+ "total_count": {
+ "description": "The total number of filing history items for this company.",
+ "type": "integer"
+ }
+ }
+ },
+ "resolution": {
+ "title": "resolution",
+ "required": [
+ "category",
+ "description",
+ "receive_date",
+ "subcategory",
+ "type"
+ ],
+ "properties": {
+ "category": {
+ "description": "The category of the resolution filed.",
+ "enum": [
+ "miscellaneous"
+ ],
+ "type": "string"
+ },
+ "description": {
+ "description": "A description of the associated filing.\n For enumeration descriptions see `description` section in the [enumeration mappings] (https://github.com/companieshouse/api-enumerations/blob/master/filing_history_descriptions.yml) file.",
+ "type": "string"
+ },
+ "document_id": {
+ "description": "The document id of the resolution.",
+ "type": "string"
+ },
+ "receive_date": {
+ "description": "The date the resolution was processed.",
+ "type": "string",
+ "format": "date"
+ },
+ "subcategory": {
+ "description": "The sub-category of the document filed.",
+ "enum": [
+ "resolution"
+ ],
+ "type": "string"
+ },
+ "type": {
+ "description": "The type of the associated filing.",
+ "type": "string"
+ }
+ }
+ }
+ }
+}
+
diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/insolvency.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/insolvency.json
new file mode 100644
index 0000000..f8d14e4
--- /dev/null
+++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/insolvency.json
@@ -0,0 +1,1316 @@
+{
+ "insolvencyCase": {
+ "get": {
+ "description": "Company insolvency information",
+ "tags": [
+ "insolvency"
+ ],
+ "parameters": [
+ {
+ "name": "company_number",
+ "in": "path",
+ "description": "The company number of the basic information to return.",
+ "required": true,
+ "type": "string"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Company insolvency resource returned",
+ "schema": {
+ "$ref": "insolvency.json#/definitions/companyInsolvency"
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "404": {
+ "description": "Resource not found"
+ }
+ }
+ }
+ },
+ "createInsolvency": {
+ "post": {
+ "summary": "Create an insolvency transaction resource",
+ "description": "Create an insolvency transaction resource",
+ "tags": [
+ "insolvencyApi"
+ ],
+ "x-operationName": "create insolvency resource",
+ "parameters": [
+ {
+ "in": "path",
+ "name": "transaction_id",
+ "required": true,
+ "description": "The transaction unique reference",
+ "type": "string"
+ },
+ {
+ "name": "request",
+ "in": "body",
+ "description": "The writable fields to create an insolvency data resource",
+ "required": false,
+ "schema": {
+ "$ref": "../models/insolvency.json#/definitions/insolvencyResourceWritable"
+ }
+ }
+ ],
+ "security": [
+ {
+ "oauth2": [
+ "https://identity.company-information.service.gov.uk/user/profile.read",
+ "https://api.company-information.service.gov.uk/company/*/insolvency.write-full"
+ ]
+ }
+ ],
+ "responses": {
+ "201": {
+ "description": "The insolvency data change resource was created.",
+ "schema": {
+ "$ref": "../models/insolvency.json#/definitions/createdInsolvencyResource"
+ }
+ },
+ "400": {
+ "description": "Bad request"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "404": {
+ "description": "Transaction not found"
+ },
+ "409": {
+ "description": "Insolvency resource already exists."
+ }
+ }
+ }
+ },
+ "insolvencyTransactionsValidation": {
+ "get": {
+ "summary": "Validate insolvency transaction resource",
+ "description": "Validate insolvency transaction resource",
+ "tags": [
+ "insolvencyApi"
+ ],
+ "x-operationName": "validate insolvency resource",
+ "parameters": [
+ {
+ "in": "path",
+ "name": "transaction_id",
+ "required": true,
+ "description": "The transaction unique reference",
+ "type": "string"
+ }
+ ],
+ "security": [
+ {
+ "oauth2": [
+ "https://identity.company-information.service.gov.uk/user/profile.read",
+ "https://api.company-information.service.gov.uk/company/*/insolvency.write-full"
+ ]
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "A validation status response was returned (note: this does not mean there were no validation errors)",
+ "schema": {
+ "$ref": "../models/errors.json#/definitions/validationStatus"
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ }
+ }
+ }
+ },
+ "practitioners": {
+ "get": {
+ "summary": "Get all practitioners",
+ "description": "Get all practitioner resources associated with a single insolvency transaction",
+ "tags": [
+ "insolvencyApi"
+ ],
+ "x-operationName": "get all practitioners",
+ "parameters": [{
+ "name": "transaction_id",
+ "in": "path",
+ "required": true,
+ "description": "The transaction that this insolvency case is applied to",
+ "type": "string"
+ }
+ ],
+ "security": [
+ {
+ "oauth2": [
+ "https://identity.company-information.service.gov.uk/user/profile.read",
+ "https://api.company-information.service.gov.uk/company/*/insolvency.write-full"
+ ]
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "An array of all practitioner resources associated with the insolvency case",
+ "schema": {
+ "$ref": "../models/insolvency.json#/definitions/allPractitioners"
+ }
+ },
+ "400": {
+ "description": "Bad request"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "404": {
+ "description": "Transaction not found"
+ }
+
+ }
+ },
+ "post": {
+ "summary": "Create a practitioner for this insolvency resource",
+ "description": "Create a practitioner for this insolvency resource",
+ "tags": [
+ "insolvencyApi"
+ ],
+ "x-operationName": "create practitioner",
+ "parameters": [
+ {
+ "in": "path",
+ "name": "transaction_id",
+ "required": true,
+ "description": "The transaction that this insolvency case is applied to",
+ "type": "string"
+ },
+ {
+ "name": "request",
+ "in": "body",
+ "description": "The writable fields to create practitioner resource",
+ "schema": {
+ "$ref": "../models/insolvency.json#/definitions/practitionerWritable"
+ }
+ }
+ ],
+ "security": [
+ {
+ "oauth2": [
+ "https://identity.company-information.service.gov.uk/user/profile.read",
+ "https://api.company-information.service.gov.uk/company/*/insolvency.write-full"
+ ]
+ }
+ ],
+ "responses": {
+ "201": {
+ "description": "Practitioner created",
+ "schema": {
+ "$ref": "../models/insolvency.json#/definitions/practitioner"
+ }
+ },
+ "400": {
+ "description": "Bad request"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ }
+ }
+ }
+ },
+ "practitionerTransactions": {
+ "get": {
+ "summary": "Get the practitioner resource",
+ "description": "Get the practitioner resource",
+ "tags": [
+ "insolvencyApi"
+ ],
+ "x-operationName": "get practitioner",
+ "parameters": [{
+ "name": "transaction_id",
+ "in": "path",
+ "required": true,
+ "description": "The transaction that this insolvency case is applied to",
+ "type": "string"
+ },
+ {
+ "name": "practitioner_id",
+ "in": "path",
+ "required": true,
+ "description": "The unique practitioner id",
+ "type": "string"
+ }
+ ],
+ "security": [
+ {
+ "oauth2": [
+ "https://identity.company-information.service.gov.uk/user/profile.read",
+ "https://api.company-information.service.gov.uk/company/*/insolvency.write-full"
+ ]
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "The practitioner resource",
+ "schema": {
+ "$ref": "../models/insolvency.json#/definitions/practitioner"
+ }
+ },
+ "400": {
+ "description": "Bad request"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "404": {
+ "description": "Transaction not found"
+ }
+
+ }
+ },
+ "delete": {
+ "summary": "Delete the practitioner from this insolvency resource",
+ "description": "Delete the practitioner from this insolvency resource",
+ "tags": [
+ "insolvencyApi"
+ ],
+ "x-operationName": "delete practitioner",
+ "parameters": [{
+ "name": "transaction_id",
+ "in": "path",
+ "required": true,
+ "description": "The transaction that this insolvency case is applied to",
+ "type": "string"
+ },
+ {
+ "name": "practitioner_id",
+ "in": "path",
+ "required": true,
+ "description": "The unique practitioner id",
+ "type": "string"
+ }
+ ],
+ "security": [
+ {
+ "oauth2": [
+ "https://identity.company-information.service.gov.uk/user/profile.read",
+ "https://api.company-information.service.gov.uk/company/*/insolvency.write-full"
+ ]
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "The practitioner was deleted"
+ },
+ "400": {
+ "description": "Bad request"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "404": {
+ "description": "Transaction not found"
+ }
+
+ }
+ }
+ },
+ "appointmentTransactions": {
+ "post": {
+ "summary": "Appoint the practitioner",
+ "description": "Appoint the practitioner",
+ "tags": [
+ "insolvencyApi"
+ ],
+ "x-operationName": "create appointment",
+ "parameters": [
+ {
+ "in": "path",
+ "name": "transaction_id",
+ "required": true,
+ "description": "The transaction that this insolvency case is applied to",
+ "type": "string"
+ },
+ {
+ "name": "practitioner_id",
+ "in": "path",
+ "required": true,
+ "description": "The unique practitioner id",
+ "type": "string"
+ },
+ {
+ "name": "request",
+ "in": "body",
+ "required": true,
+ "description": "Appointment details",
+ "schema": {
+ "$ref": "../models/insolvency.json#/definitions/appointment"
+ }
+ }
+ ],
+ "security": [
+ {
+ "oauth2": [
+ "https://identity.company-information.service.gov.uk/user/profile.read",
+ "https://api.company-information.service.gov.uk/company/*/insolvency.write-full"
+ ]
+ }
+ ],
+ "responses": {
+ "201": {
+ "description": "Practitioner appointed",
+ "schema": {
+ "$ref": "../models/insolvency.json#/definitions/practitionerAppointment"
+ }
+ },
+ "400": {
+ "description": "Bad request"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "404": {
+ "description": "Transaction not found"
+ }
+ }
+ },
+ "get": {
+ "summary": "Get the appointment details",
+ "description": "Get the appointment details",
+ "tags": [
+ "insolvencyApi"
+ ],
+ "x-operationName": "get appointment",
+ "parameters": [
+ {
+ "in": "path",
+ "name": "transaction_id",
+ "required": true,
+ "description": "The transaction that this insolvency case is applied to",
+ "type": "string"
+ },
+ {
+ "name": "practitioner_id",
+ "in": "path",
+ "required": true,
+ "description": "The unique practitioner id",
+ "type": "string"
+ }
+ ],
+ "security": [
+ {
+ "oauth2": [
+ "https://identity.company-information.service.gov.uk/user/profile.read",
+ "https://api.company-information.service.gov.uk/company/*/insolvency.write-full"
+ ]
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "The appointment details",
+ "schema": {
+ "$ref": "../models/insolvency.json#/definitions/practitionerAppointment"
+ }
+ },
+ "400": {
+ "description": "Bad request"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "404": {
+ "description": "Transaction not found"
+ }
+ }
+ },
+ "delete": {
+ "summary": "Delete the appointment resource",
+ "description": "Delete the appointment resource",
+ "tags": [
+ "insolvencyApi"
+ ],
+ "x-operationName": "delete appointment",
+ "parameters": [{
+ "name": "transaction_id",
+ "in": "path",
+ "required": true,
+ "description": "The transaction that this insolvency case is applied to",
+ "type": "string"
+ },
+ {
+ "name": "practitioner_id",
+ "in": "path",
+ "required": true,
+ "description": "The unique practitioner id",
+ "type": "string"
+ }
+ ],
+ "security": [
+ {
+ "oauth2": [
+ "https://identity.company-information.service.gov.uk/user/profile.read",
+ "https://api.company-information.service.gov.uk/company/*/insolvency.write-full"
+ ]
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "The appointment was deleted"
+ },
+ "400": {
+ "description": "Bad request"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "404": {
+ "description": "Transaction not found"
+ }
+
+ }
+ }
+ },
+ "createAttachment": {
+ "post": {
+ "summary": "Send a file attachment for the case",
+ "description": "Send a file attachment for the case",
+ "tags": [
+ "insolvencyApi"
+ ],
+ "x-operationName": "create attachment",
+ "parameters": [
+ {
+ "name": "transaction_id",
+ "in": "path",
+ "description": "The transaction unique reference",
+ "required": true,
+ "type": "string"
+ },
+ {
+ "name": "request",
+ "in": "body",
+ "required": true,
+ "description": "The attachment details",
+ "schema": {
+ "$ref": "../models/insolvency.json#/definitions/attachmentWriteable"
+ }
+ }
+ ],
+ "security": [
+ {
+ "oauth2": [
+ "https://identity.company-information.service.gov.uk/user/profile.read",
+ "https://api.company-information.service.gov.uk/company/*/insolvency.write-full"
+ ]
+ }
+ ],
+ "responses": {
+ "201": {
+ "description": "The file was accepted for processing",
+ "schema": {
+ "$ref": "../models/insolvency.json#/definitions/createdAttachment"
+ }
+ },
+ "400": {
+ "description": "Bad request"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "404": {
+ "description": "Transaction not found"
+ }
+ }
+ }
+ },
+ "attachmentTransactions": {
+ "get": {
+ "summary": "Get information about the attachment that was submitted",
+ "description": "Get information about the attachment that was submitted",
+ "tags": [
+ "insolvencyApi"
+ ],
+ "x-operationName": "get attachment",
+ "parameters": [
+ {
+ "name": "transaction_id",
+ "in": "path",
+ "description": "The transaction unique reference",
+ "required": true,
+ "type": "string"
+ },
+ {
+ "name": "attachment_id",
+ "in": "path",
+ "required": true,
+ "description": "The unique attachment id",
+ "type": "string"
+ }
+ ],
+ "security": [
+ {
+ "oauth2": [
+ "https://identity.company-information.service.gov.uk/user/profile.read",
+ "https://api.company-information.service.gov.uk/company/*/insolvency.write-full"
+ ]
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "the attachment resource",
+ "schema": {
+ "$ref": "../models/insolvency.json#/definitions/createdAttachment"
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "404": {
+ "description": "attachment not found"
+ }
+ }
+ },
+ "delete": {
+ "summary": "Delete an attachment from this transaction",
+ "description": "Delete an attachment from this transaction",
+ "tags": [
+ "insolvencyApi"
+ ],
+ "x-operationName": "delete attachment",
+ "parameters": [
+ {
+ "name": "transaction_id",
+ "in": "path",
+ "description": "The transaction unique reference",
+ "required": true,
+ "type": "string"
+ },
+ {
+ "name": "attachment_id",
+ "in": "path",
+ "required": true,
+ "description": "The unique attachment id",
+ "type": "string"
+ }
+ ],
+ "security": [
+ {
+ "oauth2": [
+ "https://identity.company-information.service.gov.uk/user/profile.read",
+ "https://api.company-information.service.gov.uk/company/*/insolvency.write-full"
+ ]
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "The attachment was deleted"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ }
+ }
+ }
+ },
+ "createResolution": {
+ "post": {
+ "summary": "Send resolution details for this transaction",
+ "description": "Send resolution details for this transaction",
+ "tags": [
+ "insolvencyApi"
+ ],
+ "x-operationName": "create resolution",
+ "parameters": [
+ {
+ "name": "transaction_id",
+ "in": "path",
+ "description": "The transaction unique reference",
+ "required": true,
+ "type": "string"
+ },
+ {
+ "name": "request",
+ "in": "body",
+ "required": true,
+ "description": "The resolution details",
+ "schema": {
+ "$ref": "../models/insolvency.json#/definitions/resolutionResourceWriteable"
+ }
+ }
+ ],
+ "security": [
+ {
+ "oauth2": [
+ "https://identity.company-information.service.gov.uk/user/profile.read",
+ "https://api.company-information.service.gov.uk/company/*/insolvency.write-full"
+ ]
+ }
+ ],
+ "responses": {
+ "201": {
+ "description": "The resolution details was sent correctly",
+ "schema": {
+ "$ref": "../models/insolvency.json#/definitions/Resolution"
+ }
+ },
+ "400": {
+ "description": "Bad request"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "500": {
+ "description": "attachment not found on transaction"
+ }
+ }
+ },
+ "get": {
+ "summary": "Get the resolution details",
+ "description": "Get the resolution details for the transaction",
+ "tags": [
+ "insolvencyApi"
+ ],
+ "x-operationName": "get resolution details",
+ "parameters": [
+ {
+ "name": "transaction_id",
+ "in": "path",
+ "description": "The transaction unique reference",
+ "required": true,
+ "type": "string"
+ }
+ ],
+ "security": [
+ {
+ "oauth2": [
+ "https://identity.company-information.service.gov.uk/user/profile.read",
+ "https://api.company-information.service.gov.uk/company/*/insolvency.write-full"
+ ]
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "The resolution details",
+ "schema": {
+ "$ref": "../models/insolvency.json#/definitions/Resolution"
+ }
+ },
+ "400": {
+ "description": "Bad request"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "404": {
+ "description": "resolution not found on transaction"
+ }
+ }
+ },
+ "delete": {
+ "summary": "Delete the resolution date",
+ "description": "Delete the resolution date for the transaction",
+ "tags": [
+ "insolvencyApi"
+ ],
+ "x-operationName": "delete resolution",
+ "parameters": [
+ {
+ "name": "transaction_id",
+ "in": "path",
+ "description": "The transaction unique reference",
+ "required": true,
+ "type": "string"
+ }
+ ],
+ "security": [
+ {
+ "oauth2": [
+ "https://identity.company-information.service.gov.uk/user/profile.read",
+ "https://api.company-information.service.gov.uk/company/*/insolvency.write-full"
+ ]
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "The resolution date was deleted"
+ },
+ "400": {
+ "description": "Bad request"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "404": {
+ "description": "Not found"
+ }
+ }
+ }
+ },
+ "downloadAttachment": {
+ "get": {
+ "summary": "Download the attachment",
+ "description": "Download an attachment",
+ "tags": [
+ "insolvencyApi"
+ ],
+ "x-operationName": "download attachment",
+ "parameters": [
+ {
+ "name": "transaction_id",
+ "in": "path",
+ "description": "The transaction unique reference",
+ "required": true,
+ "type": "string"
+ },
+ {
+ "name": "attachment_id",
+ "in": "path",
+ "description": "The unique attachment id",
+ "required": true,
+ "type": "string"
+ }
+ ],
+ "security": [
+ {
+ "oauth2": [
+ "https://identity.company-information.service.gov.uk/user/profile.read",
+ "https://api.company-information.service.gov.uk/company/*/insolvency.write-full"
+ ]
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "The file will begin to download",
+ "schema": {
+ "$ref": "../models/insolvency.json#/definitions/downloadedAttachment"
+ }
+ },
+ "404": {
+ "description": "attachment not found on transaction"
+ }
+ }
+ }
+
+ },
+ "statementOfAffairs": {
+ "post": {
+ "summary": "Send statement of affairs details for this transaction",
+ "description": "Create the statement of affairs for this transaction",
+ "tags": [
+ "insolvencyApi"
+ ],
+ "x-operationName": "create statement of affairs",
+ "parameters": [
+ {
+ "name": "transaction_id",
+ "in": "path",
+ "description": "The transaction unique reference",
+ "required": true,
+ "type": "string"
+ },
+ {
+ "name": "request",
+ "in":"body",
+ "required":true,
+ "description":"The statement of affairs date",
+ "schema": {
+ "$ref": "../models/insolvency.json#/definitions/statementOfAffairsWriteable"
+ }
+ }
+ ],
+ "security": [
+ {
+ "oauth2": [
+ "https://identity.company-information.service.gov.uk/user/profile.read",
+ "https://api.company-information.service.gov.uk/company/*/insolvency.write-full"
+ ]
+ }
+ ],
+ "responses": {
+ "201": {
+ "description": "The statement of affairs date was sent correctly",
+ "schema": {
+ "$ref": "../models/insolvency.json#/definitions/statementOfAffairs"
+ }
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "400": {
+ "description": "Bad request"
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "500": {
+ "description": "attachment not found on transaction"
+ }
+ }
+ },
+ "get": {
+ "summary": "Get Statement of Affairs",
+ "description": "Get the statement of affairs details for the transaction",
+ "tags": [
+ "insolvencyApi"
+ ],
+ "x-operationName": "get statement of affairs",
+ "parameters": [
+ {
+ "name": "transaction_id",
+ "in": "path",
+ "description": "The transaction unique reference",
+ "required": true,
+ "type": "string"
+ }
+ ],
+ "security": [
+ {
+ "oauth2": [
+ "https://identity.company-information.service.gov.uk/user/profile.read",
+ "https://api.company-information.service.gov.uk/company/*/insolvency.write-full"
+ ]
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "The statement of affairs details",
+ "schema": {
+ "$ref": "../models/insolvency.json#/definitions/statementOfAffairs"
+ }
+ },
+ "400": {
+ "description": "Bad request"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "404": {
+ "description": "The statement of affairs was not found"
+ }
+ }
+ },
+ "delete": {
+ "summary":"Delete the statement of affairs",
+ "description": "Delete the statement of affairs date for the transaction",
+ "tags": [
+ "insolvencyApi"
+ ],
+ "x-operationName": "delete statement of affairs",
+ "parameters": [
+ {
+ "name": "transaction_id",
+ "in": "path",
+ "description": "The transaction unique reference",
+ "required": true,
+ "type": "string"
+ }
+ ],
+ "security": [
+ {
+ "oauth2": [
+ "https://identity.company-information.service.gov.uk/user/profile.read",
+ "https://api.company-information.service.gov.uk/company/*/insolvency.write-full"
+ ]
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "The statement of affairs date was deleted"
+ },
+ "400": {
+ "description": "Bad request"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "404": {
+ "description": "Not found"
+ }
+ }
+ }
+ },
+ "progressReport": {
+ "post": {
+ "summary": "Send progress report details for this transaction",
+ "description": "Create the progress report for this transaction",
+ "tags": [
+ "insolvencyApi"
+ ],
+ "x-operationName": "create progress report",
+ "parameters": [
+ {
+ "name": "transaction_id",
+ "in": "path",
+ "description": "The transaction unique reference",
+ "required": true,
+ "type": "string"
+ },
+ {
+ "name": "request",
+ "in": "body",
+ "required": true,
+ "description": "The progress report dates and attachment ID",
+ "schema": {
+ "$ref": "../models/insolvency.json#/definitions/progressReportWriteable"
+ }
+ }
+ ],
+ "security": [
+ {
+ "oauth2": [
+ "https://identity.company-information.service.gov.uk/user/profile.read",
+ "https://api.company-information.service.gov.uk/company/*/insolvency.write-full"
+ ]
+ }
+ ],
+ "responses": {
+ "201": {
+ "description": "Progress report resource created",
+ "content": "application/json",
+ "schema": {
+ "$ref": "../models/insolvency.json#/definitions/progressReport"
+ }
+ },
+ "400": {
+ "description": "Bad request"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "500": {
+ "description": "attachment not found on transaction"
+ }
+ }
+ },
+ "get": {
+ "summary": "Get Progress Report",
+ "description": "Get the progress report details for the transaction",
+ "tags": [
+ "insolvencyApi"
+ ],
+ "x-operationName": "get progress report",
+ "parameters": [
+ {
+ "name": "transaction_id",
+ "in": "path",
+ "description": "The transaction unique reference",
+ "required": true,
+ "type": "string"
+ }
+ ],
+ "security": [
+ {
+ "oauth2": [
+ "https://identity.company-information.service.gov.uk/user/profile.read",
+ "https://api.company-information.service.gov.uk/company/*/insolvency.write-full"
+ ]
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "The progress report details",
+ "schema": {
+ "$ref": "../models/insolvency.json#/definitions/progressReport"
+ }
+ },
+ "400": {
+ "description": "Bad request"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "404": {
+ "description": "The progress report was not found"
+ }
+ }
+ },
+ "delete": {
+ "summary": "Delete the progress report",
+ "description": "Delete the progress report dates for the transaction",
+ "tags": [
+ "insolvencyApi"
+ ],
+ "x-operationName": "delete progress report",
+ "parameters": [
+ {
+ "name": "transaction_id",
+ "in": "path",
+ "description": "The transaction unique reference",
+ "required": true,
+ "type": "string"
+ }
+ ],
+ "security": [
+ {
+ "oauth2": [
+ "https://identity.company-information.service.gov.uk/user/profile.read",
+ "https://api.company-information.service.gov.uk/company/*/insolvency.write-full"
+ ]
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "The progress report dates were deleted"
+ },
+ "400": {
+ "description": "Bad request"
+ },
+ "401": {
+ "description": "Unauthorized"
+ },
+ "403": {
+ "description": "Forbidden"
+ },
+ "404": {
+ "description": "Not found"
+ }
+ }
+ }
+ },
+ "definitions": {
+ "companyInsolvency": {
+ "title": "companyInsolvency",
+ "required": [
+ "etag",
+ "cases"
+ ],
+ "properties": {
+ "etag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ },
+ "cases": {
+ "type": "array",
+ "description": "List of insolvency cases.",
+ "items": {
+ "$ref": "insolvency.json#/definitions/case"
+ }
+ },
+ "status": {
+ "type": "string",
+ "description": "Company insolvency status details",
+ "enum": [
+ "administration-order",
+ "administrative-receiver",
+ "in-administration",
+ "liquidation",
+ "live-receiver-manager-on-at-least-one-charge",
+ "receivership",
+ "receiver-manager",
+ "voluntary-arrangement",
+ "voluntary-arrangement-receivership"
+ ]
+ }
+ }
+ },
+ "case": {
+ "title": "case",
+ "required": [
+ "type",
+ "dates",
+ "practitioners"
+ ],
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "compulsory-liquidation",
+ "creditors-voluntary-liquidation",
+ "members-voluntary-liquidation",
+ "in-administration",
+ "corporate-voluntary-arrangement",
+ "corporate-voluntary-arrangement-moratorium",
+ "administration-order",
+ "receiver-manager",
+ "administrative-receiver",
+ "receivership",
+ "foreign-insolvency",
+ "moratorium"
+ ],
+ "description": "The type of case.\n For enumeration descriptions see `insolvency_case_type` section in the [enumeration mappings] (https://github.com/companieshouse/api-enumerations/blob/master/constants.yml)."
+ },
+ "dates": {
+ "type": "array",
+ "description": "The dates specific to the case.",
+ "items": {
+ "$ref": "insolvency.json#/definitions/caseDates"
+ }
+ },
+ "notes": {
+ "type": "array",
+ "description": "The dates specific to the case.",
+ "items": {
+ "type": "string"
+ }
+ },
+ "practitioners": {
+ "type": "array",
+ "description": "The practitioners for the case.",
+ "items": {
+ "$ref": "insolvency.json#/definitions/practitioners"
+ }
+ },
+ "links": {
+ "type": "object",
+ "description": "The practitioners for the case.",
+ "items": {
+ "$ref": "insolvency.json#/definitions/links"
+ }
+ },
+ "number": {
+ "type": "string",
+ "description": "The case number."
+ }
+ }
+ },
+ "caseDates": {
+ "title": "caseDates",
+ "required": [
+ "type",
+ "date"
+ ],
+ "properties": {
+ "type": {
+ "type": "string",
+ "description": "Describes what date is represented by the associated `date` element.\n For enumeration descriptions see `insolvency_case_date_type` section in the [enumeration mappings] (https://github.com/companieshouse/api-enumerations/blob/master/constants.yml).",
+ "enum": [
+ "instrumented-on",
+ "administration-started-on",
+ "administration-discharged-on",
+ "administration-ended-on",
+ "concluded-winding-up-on",
+ "petitioned-on",
+ "ordered-to-wind-up-on",
+ "due-to-be-dissolved-on",
+ "case-end-on",
+ "wound-up-on",
+ "voluntary-arrangement-started-on",
+ "voluntary-arrangement-ended-on",
+ "moratorium-started-on",
+ "moratorium-ended-on",
+ "declaration-solvent-on"
+ ]
+ },
+ "date": {
+ "type": "string",
+ "format": "date",
+ "description": "The case date, described by `date_type`."
+ }
+ }
+ },
+ "practitioners": {
+ "title": "practitioners",
+ "required": [
+ "name",
+ "address"
+ ],
+ "properties": {
+ "name": {
+ "description": "The name of the practitioner.",
+ "type": "string"
+ },
+ "address": {
+ "type": "array",
+ "description": "The practitioners' address.",
+ "items": {
+ "$ref": "insolvency.json#/definitions/practitionerAddress"
+ }
+ },
+ "appointed_on": {
+ "type": "string",
+ "format": "date",
+ "description": "The date the practitioner was appointed on."
+ },
+ "ceased_to_act_on": {
+ "type": "string",
+ "format": "date",
+ "description": "The date the practitioner ceased to act for the case."
+ },
+ "role": {
+ "type": "string",
+ "description": "The type of role.",
+ "enum": [
+ "final-liquidator",
+ "receiver",
+ "receiver-manager",
+ "proposed-liquidator",
+ "provisional-liquidator",
+ "administrative-receiver",
+ "practitioner",
+ "interim-liquidator"
+ ]
+ }
+ }
+ },
+ "practitionerAddress": {
+ "title": "practitionerAddress",
+ "required": [
+ "address_line_1"
+ ],
+ "properties": {
+ "address_line_1": {
+ "type": "string",
+ "description": "The first line of the address."
+ },
+ "address_line_2": {
+ "type": "string",
+ "description": "The second line of the address."
+ },
+ "locality": {
+ "type": "string",
+ "description": "The locality. For example London."
+ },
+ "region": {
+ "type": "string",
+ "description": "The region. For example Surrey."
+ },
+ "postal_code": {
+ "type": "string",
+ "description": "The postal code. For example CF14 3UZ."
+ },
+ "country": {
+ "type": "string",
+ "description": "The country."
+ }
+ }
+ },
+ "links": {
+ "title": "links",
+ "properties": {
+ "charge": {
+ "type": "string",
+ "description": "The link to the charge this case is lodged against."
+ }
+ }
+ }
+ }
+}
+
+
+
diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/officerAppointmentList.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/officerAppointmentList.json
new file mode 100644
index 0000000..8723a87
--- /dev/null
+++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/officerAppointmentList.json
@@ -0,0 +1,452 @@
+{
+ "get": {
+ "summary": "Officer Appointment List",
+ "tags": [
+ "officerAppointments"
+ ],
+ "x-operationName": "list",
+ "description": "List of all officer appointments",
+ "parameters": [
+ {
+ "name": "officer_id",
+ "in": "path",
+ "description": "The officer id of the appointment list being requested.",
+ "required": true,
+ "type": "string"
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Use “active” to return only active appointments.",
+ "type": "string"
+ },
+ {
+ "name": "items_per_page",
+ "in": "query",
+ "description": "The number of appointments to return per page.",
+ "type": "integer"
+ },
+ {
+ "name": "start_index",
+ "in": "query",
+ "description": "The first row of data to retrieve, starting at 0. Use this parameter as a pagination mechanism along with the items_per_page parameter.",
+ "type": "integer"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "List the officer appointments",
+ "headers": {
+ "ETag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ }
+ },
+ "schema": {
+ "$ref": "officerAppointmentList.json#/definitions/appointmentList"
+ }
+ },
+ "400": {
+ "description": "Bad request"
+ },
+ "401": {
+ "description": "Unauthorised"
+ }
+ }
+ },
+ "definitions": {
+ "appointmentList": {
+ "title": "appointmentList",
+ "properties": {
+ "date_of_birth": {
+ "description": "The officer's date of birth details.",
+ "items": {
+ "$ref": "officerAppointmentList.json#/definitions/dateOfBirth"
+ },
+ "type": "object"
+ },
+ "etag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ },
+ "is_corporate_officer": {
+ "description": "Indicator representing if the officer is a corporate body.",
+ "type": "boolean"
+ },
+ "items": {
+ "description": "The list of officer appointments.",
+ "type": "array",
+ "items": {
+ "$ref": "officerAppointmentList.json#/definitions/officerAppointmentSummary"
+ }
+ },
+ "items_per_page": {
+ "description": "The number of officer appointments to return per page.",
+ "type": "integer"
+ },
+ "kind": {
+ "enum": [
+ "personal-appointment"
+ ],
+ "type": "string"
+ },
+ "links": {
+ "description": "Links to other resources associated with this officer appointment resource.",
+ "items": {
+ "$ref": "officerAppointmentList.json#/definitions/officerLinkTypes"
+ },
+ "type": "object"
+ },
+ "name": {
+ "description": "The corporate or natural officer name.",
+ "type": "string"
+ },
+ "start_index": {
+ "description": "The first row of data to retrieve, starting at 0. Use this parameter as a pagination mechanism along with the `items_per_page` parameter.",
+ "type": "integer"
+ },
+ "total_results": {
+ "description": "The total number of officer appointments in this result set.",
+ "type": "integer"
+ }
+ },
+ "required": [
+ "etag",
+ "is_corporate_officer",
+ "items",
+ "items_per_page",
+ "kind",
+ "links",
+ "name",
+ "start_index",
+ "total_results"
+ ]
+ },
+ "officerAppointmentSummary": {
+ "title": "officerAppointmentSummary",
+ "properties": {
+ "address": {
+ "description": "The correspondence address of the officer.",
+ "items": {
+ "$ref": "officerAppointmentList.json#/definitions/address"
+ },
+ "type": "object"
+ },
+ "appointed_before": {
+ "description": "The date the officer was appointed before. Only present when the `is_pre_1992_appointment` attribute is `true`.",
+ "type": "string",
+ "format": "date"
+ },
+ "appointed_on": {
+ "description": "The date on which the officer was appointed. For the officer roles of `corporate-managing-officer` and `managing-officer` this is the date on which Companies House was notified about the officer.",
+ "type": "string",
+ "format": "date"
+ },
+ "appointed_to": {
+ "description": "The company information of the appointment.",
+ "items": {
+ "$ref": "officerAppointmentList.json#/definitions/appointedTo"
+ },
+ "type": "object"
+ },
+ "contact_details": {
+ "description": "The contact at the `corporate-managing-officer` of a `registered-overseas-entity`.",
+ "items": {
+ "$ref": "officerAppointmentList.json#/definitions/contactDetails"
+ },
+ "type": "object"
+ },
+ "name": {
+ "description": "The full name of the officer.",
+ "type": "string"
+ },
+ "country_of_residence": {
+ "description": "The officer's country of residence.",
+ "type": "string"
+ },
+ "former_names": {
+ "description": "Former names for the officer, if there are any.",
+ "items": {
+ "$ref": "officerAppointmentList.json#/definitions/formerNames"
+ },
+ "type": "array"
+ },
+ "identification": {
+ "description": "Only one from `eea`, `non-eea`, `uk-limited-company`, `other-corporate-body-or-firm` or `registered-overseas-entity-corporate-managing-officer` can be supplied, not multiples of them.",
+ "items": {
+ "$ref": "officerAppointmentList.json#/definitions/corporateIdent"
+ },
+ "type": "object"
+ },
+ "identity_verification_details": {
+ "description": "Information relating to the identity verification of the officer",
+ "items": {
+ "$ref": "../models/officerChanges.json#/definitions/identityVerificationDetails"
+ },
+ "type": "object"
+ },
+ "is_pre_1992_appointment": {
+ "description": "Indicator representing if the officer was appointed before their appointment date.",
+ "type": "boolean"
+ },
+ "links": {
+ "description": "Links to other resources associated with this officer appointment item.",
+ "type": "object",
+ "items": {
+ "$ref": "officerAppointmentList.json#/definitions/appointmentLinkTypes"
+ }
+ },
+ "name_elements": {
+ "description": "A document encapsulating the separate elements of a natural officer's name.",
+ "type": "object",
+ "items": {
+ "$ref": "officerAppointmentList.json#/definitions/nameElements"
+ }
+ },
+ "nationality": {
+ "description": "The officer's nationality.",
+ "type": "string"
+ },
+ "occupation": {
+ "description": "The officer's occupation.",
+ "type": "string"
+ },
+ "officer_role": {
+ "enum": [
+ "cic-manager",
+ "corporate-director",
+ "corporate-llp-designated-member",
+ "corporate-llp-member",
+ "corporate-managing-officer",
+ "corporate-member-of-a-management-organ",
+ "corporate-member-of-a-supervisory-organ",
+ "corporate-member-of-an-administrative-organ",
+ "corporate-nominee-director",
+ "corporate-nominee-secretary",
+ "corporate-secretary",
+ "director",
+ "judicial-factor",
+ "llp-designated-member",
+ "llp-member",
+ "managing-officer",
+ "member-of-a-management-organ",
+ "member-of-a-supervisory-organ",
+ "member-of-an-administrative-organ",
+ "nominee-director",
+ "nominee-secretary",
+ "receiver-and-manager",
+ "secretary"
+ ],
+ "type": "string"
+ },
+ "principal_office_address": {
+ "description": "The principal/registered office address of a `corporate-managing-officer` of a `registered-overseas-entity`.",
+ "items": {
+ "$ref": "officerAppointmentList.json#/definitions/address"
+ },
+ "type": "object"
+ },
+ "resigned_on": {
+ "description": "The date the officer was resigned. For the officer roles of `corporate-managing-officer` and `managing-officer` this is the date on which Companies House was notified about the officers cessation.",
+ "type": "string",
+ "format": "date"
+ },
+ "responsibilities": {
+ "description": "The responsibilities of the managing officer of a `registered-overseas-entity`.",
+ "type": "string"
+ }
+ },
+ "required": [
+ "appointed_to",
+ "links",
+ "name",
+ "officer_role"
+ ]
+ },
+ "address": {
+ "title": "address",
+ "properties": {
+ "address_line_1": {
+ "description": "The first line of the address.",
+ "type": "string"
+ },
+ "address_line_2": {
+ "description": "The second line of the address.",
+ "type": "string"
+ },
+ "care_of": {
+ "description": "The care of name.",
+ "type": "string"
+ },
+ "country": {
+ "description": "The country. For example, UK.",
+ "type": "string"
+ },
+ "locality": {
+ "description": "The locality. For example London.",
+ "type": "string"
+ },
+ "po_box": {
+ "description": "The post-office box number.",
+ "type": "string"
+ },
+ "postal_code": {
+ "description": "The postal code. For example CF14 3UZ.",
+ "type": "string"
+ },
+ "premises": {
+ "description": "The property name or number.",
+ "type": "string"
+ },
+ "region": {
+ "description": "The region. For example Surrey.",
+ "type": "string"
+ }
+ }
+ },
+ "appointedTo": {
+ "title": "appointedTo",
+ "properties": {
+ "company_name": {
+ "description": "The name of the company the officer is acting for.",
+ "type": "string"
+ },
+ "company_number": {
+ "description": "The number of the company the officer is acting for.",
+ "type": "string"
+ },
+ "company_status": {
+ "description": "The status of the company the officer is acting for.",
+ "type": "string"
+ }
+ },
+ "required": [
+ "company_number"
+ ]
+ },
+ "contactDetails": {
+ "title": "contactDetails",
+ "properties": {
+ "contact_name": {
+ "description": "The name of the contact.",
+ "type": "string"
+ }
+ }
+ },
+ "dateOfBirth": {
+ "title": "dateOfBirth",
+ "properties": {
+ "month": {
+ "description": "The month the officer was born in.",
+ "type": "integer"
+ },
+ "year": {
+ "description": "The year the officer was born in.",
+ "type": "integer"
+ }
+ },
+ "required": [
+ "month",
+ "year"
+ ]
+ },
+ "formerNames": {
+ "title": "formerNames",
+ "properties": {
+ "forenames": {
+ "description": "Former forenames of the officer.",
+ "type": "string"
+ },
+ "surname": {
+ "description": "Former surnames of the officer.",
+ "type": "string"
+ }
+ }
+ },
+ "officerLinkTypes": {
+ "title": "officerLinkTypes",
+ "required": [
+ "self"
+ ],
+ "properties": {
+ "self": {
+ "description": "Link to this officer appointment resource.",
+ "type": "string"
+ }
+ }
+ },
+ "appointmentLinkTypes": {
+ "title": "appointmentLinkTypes",
+ "required": [
+ "company"
+ ],
+ "properties": {
+ "company": {
+ "description": "Link to the company profile resource that this appointment is associated with.",
+ "type": "string"
+ }
+ }
+ },
+ "corporateIdent": {
+ "title": "corporateIdent",
+ "properties": {
+ "identification_type": {
+ "description": "The officer's identity type",
+ "enum": [
+ "eea",
+ "non-eea",
+ "uk-limited-company",
+ "other-corporate-body-or-firm",
+ "registered-overseas-entity-corporate-managing-officer"
+ ],
+ "type": "string"
+ },
+ "legal_authority": {
+ "description": "The legal authority supervising the company.",
+ "type": "string"
+ },
+ "legal_form": {
+ "description": "The legal form of the company as defined by its country of registration.",
+ "type": "string"
+ },
+ "place_registered": {
+ "description": "Place registered.",
+ "type": "string"
+ },
+ "registration_number": {
+ "description": "Company registration number.",
+ "type": "string"
+ }
+ }
+ },
+ "nameElements": {
+ "title": "nameElements",
+ "properties": {
+ "forename": {
+ "description": "The forename of the officer.",
+ "type": "string"
+ },
+ "title": {
+ "description": "Title of the officer.",
+ "type": "string"
+ },
+ "other_forenames": {
+ "description": "Other forenames of the officer.",
+ "type": "string"
+ },
+ "surname": {
+ "description": "The surname of the officer.",
+ "type": "string"
+ },
+ "honours": {
+ "description": "Honours an officer might have.",
+ "type": "string"
+ }
+ },
+ "required": [
+ "surname"
+ ]
+ }
+ }
+}
+
diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json
new file mode 100644
index 0000000..354843f
--- /dev/null
+++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json
@@ -0,0 +1,538 @@
+{
+ "listCompanyPSC": {
+ "get": {
+ "summary": "List the company persons with significant control",
+ "description": "List of all persons with significant control (not statements)",
+ "x-operationName": "list",
+ "parameters": [
+ {
+ "name": "company_number",
+ "in": "path",
+ "description": "The company number of the persons with significant control list being requested.",
+ "required": true,
+ "type": "string"
+ },
+ {
+ "name": "items_per_page",
+ "in": "query",
+ "description": "The number of persons with significant control to return per page.",
+ "required": true,
+ "type": "string"
+ },
+ {
+ "name": "start_index",
+ "in": "query",
+ "description": "The offset into the entire result set that this page starts.",
+ "required": true,
+ "type": "string"
+ },
+ {
+ "name": "register_view",
+ "in": "query",
+ "description": "Display register specific information. If register is held at Companies House and register_view is set to true, only PSCs which are active or were terminated during election period are shown. Accepted values are: -`true` \n -`false` \n Defaults to false.",
+ "required": true,
+ "type": "string"
+ }
+ ],
+ "tags": [
+ "personsWithSignificantControl"
+ ],
+ "responses": {
+ "200": {
+ "description": "readCompanyProfile",
+ "headers": {
+ "ETag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ }
+ },
+ "schema": {
+ "$ref": "pscModels.json#/definitions/list"
+ }
+ },
+ "401": {
+ "description": "Unauthorised"
+ },
+ "404": {
+ "description": "Resource not found"
+ }
+ }
+ }
+ },
+ "getIndividualPSC": {
+ "get": {
+ "summary": "Get the individual person with significant control notification",
+ "description": "Get details of the individual person with significant control notification",
+ "x-operationName": "get individual",
+ "parameters": [
+ {
+ "name": "company_number",
+ "in": "path",
+ "description": "The company number of the person with significant control details being requested.",
+ "required": true,
+ "type": "string"
+ },
+ {
+ "name": "notification_id",
+ "in": "path",
+ "description": "The notification id of the person with significant control notification being requested.",
+ "required": true,
+ "type": "string"
+ }
+ ],
+ "tags": [
+ "personsWithSignificantControl"
+ ],
+ "responses": {
+ "200": {
+ "description": "IndividualPSC resource returned",
+ "headers": {
+ "ETag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ }
+ },
+ "schema": {
+ "$ref": "pscModels.json#/definitions/individual"
+ }
+ },
+ "401": {
+ "description": "Unauthorised"
+ },
+ "404": {
+ "description": "Resource not found"
+ }
+ }
+ }
+ },
+ "getIndividualBO": {
+ "get": {
+ "summary": "Get the individual beneficial owner notification",
+ "description": "Get details of an individual beneficial owner notification",
+ "x-operationName": "get individual beneficial owner",
+ "parameters": [
+ {
+ "name": "company_number",
+ "in": "path",
+ "description": "The company number of the individual beneficial owner details being requested.",
+ "required": true,
+ "type": "string"
+ },
+ {
+ "name": "notification_id",
+ "in": "path",
+ "description": "The notification id of the individual beneficial owner notification being requested.",
+ "required": true,
+ "type": "string"
+ }
+ ],
+ "tags": [
+ "personsWithSignificantControl"
+ ],
+ "responses": {
+ "200": {
+ "description": "IndividualBO resource returned",
+ "headers": {
+ "ETag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ }
+ },
+ "schema": {
+ "$ref": "pscModels.json#/definitions/individualBeneficialOwner"
+ }
+ },
+ "401": {
+ "description": "Unauthorised"
+ },
+ "404": {
+ "description": "Resource not found"
+ }
+ }
+ }
+ },
+ "getCorporateEntityPSC": {
+ "get": {
+ "summary": "Get the corporate entity with significant control notification",
+ "description": "Get details of a corporate entity with significant control notification",
+ "x-operationName": "get corporate entities",
+ "parameters": [
+ {
+ "name": "company_number",
+ "in": "path",
+ "description": "The company number of the corporate entity with significant control details being requested.",
+ "required": true,
+ "type": "string"
+ },
+ {
+ "name": "notification_id",
+ "in": "path",
+ "description": "The notification id of the corporate entity with significant control notification being requested.",
+ "required": true,
+ "type": "string"
+ }
+ ],
+ "tags": [
+ "personsWithSignificantControl"
+ ],
+ "responses": {
+ "200": {
+ "description": "CorporateEntityPSC resource returned",
+ "headers": {
+ "ETag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ }
+ },
+ "schema": {
+ "$ref": "pscModels.json#/definitions/corporateEntity"
+ }
+ },
+ "401": {
+ "description": "Unauthorised"
+ },
+ "404": {
+ "description": "Resource not found"
+ }
+ }
+ }
+ },
+ "getCorporateEntityBO": {
+ "get": {
+ "summary": "Get the corporate entity beneficial owner notification",
+ "description": "Get details of the corporate entity beneficial owner notification",
+ "x-operationName": "get corporate entity beneficial owner",
+ "parameters": [
+ {
+ "name": "company_number",
+ "in": "path",
+ "description": "The company number of the corporate entity beneficial owner details being requested.",
+ "required": true,
+ "type": "string"
+ },
+ {
+ "name": "notification_id",
+ "in": "path",
+ "description": "The notification id of the corporate entity beneficial owner notification being requested.",
+ "required": true,
+ "type": "string"
+ }
+ ],
+ "tags": [
+ "personsWithSignificantControl"
+ ],
+ "responses": {
+ "200": {
+ "description": "CorporateEntityBO resource returned",
+ "headers": {
+ "ETag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ }
+ },
+ "schema": {
+ "$ref": "pscModels.json#/definitions/corporateEntityBeneficialOwner"
+ }
+ },
+ "401": {
+ "description": "Unauthorised"
+ },
+ "404": {
+ "description": "Resource not found"
+ }
+ }
+ }
+ },
+ "getLegalPersonPSC": {
+ "get": {
+ "summary": "Get the legal person with significant control notification",
+ "description": "Get details of the legal person with significant control notification",
+ "x-operationName": "get legal persons",
+ "parameters": [
+ {
+ "name": "company_number",
+ "in": "path",
+ "description": "The company number of the legal person with significant control details being requested.",
+ "required": true,
+ "type": "string"
+ },
+ {
+ "name": "notification_id",
+ "in": "path",
+ "description": "The notification id of the legal person with significant control notification being requested.",
+ "required": true,
+ "type": "string"
+ }
+ ],
+ "tags": [
+ "personsWithSignificantControl"
+ ],
+ "responses": {
+ "200": {
+ "description": "LegalPersonPSC resource returned",
+ "headers": {
+ "ETag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ }
+ },
+ "schema": {
+ "$ref": "pscModels.json#/definitions/legalPerson"
+ }
+ },
+ "401": {
+ "description": "Unauthorised"
+ },
+ "404": {
+ "description": "Resource not found"
+ }
+ }
+ }
+ },
+ "getLegalPersonBO": {
+ "get": {
+ "summary": "Get the legal person beneficial owner notification",
+ "description": "Get details of the legal person beneficial owner notification",
+ "x-operationName": "get legal person beneficial owner",
+ "parameters": [
+ {
+ "name": "company_number",
+ "in": "path",
+ "description": "The company number of the legal person beneficial owner details being requested.",
+ "required": true,
+ "type": "string"
+ },
+ {
+ "name": "notification_id",
+ "in": "path",
+ "description": "The notification id of the legal person beneficial owner notification being requested.",
+ "required": true,
+ "type": "string"
+ }
+ ],
+ "tags": [
+ "personsWithSignificantControl"
+ ],
+ "responses": {
+ "200": {
+ "description": "LegalPersonBO resource returned",
+ "headers": {
+ "ETag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ }
+ },
+ "schema": {
+ "$ref": "pscModels.json#/definitions/legalPersonBeneficialOwner"
+ }
+ },
+ "401": {
+ "description": "Unauthorised"
+ },
+ "404": {
+ "description": "Resource not found"
+ }
+ }
+ }
+ },
+ "listCompanyPSCStatements": {
+ "get": {
+ "summary": "List the company persons with significant control statements",
+ "description": "List of all persons with significant control statements",
+ "x-operationName": "list statements",
+ "parameters": [
+ {
+ "name": "company_number",
+ "in": "path",
+ "description": "The company number of the persons with significant control statements list being requested.",
+ "required": true,
+ "type": "string"
+ },
+ {
+ "name": "items_per_page",
+ "in": "query",
+ "description": "The id of the legal person with significant control details being requested.",
+ "required": true,
+ "type": "integer"
+ },
+ {
+ "name": "start_index",
+ "in": "query",
+ "description": "The offset into the entire result set that this page starts.",
+ "required": true,
+ "type": "integer"
+ },
+ {
+ "name": "register_view",
+ "in": "query",
+ "description": "Display register specific information. If register is held at Companies House and register_view is set to true, only statements which are active or were withdrawn during election period are shown. Accepted values are: -`true` \n -`false` \n Defaults to false.",
+ "required": true,
+ "type": "query"
+ }
+ ],
+ "tags": [
+ "personsWithSignificantControl"
+ ],
+ "responses": {
+ "200": {
+ "description": "CompanyPSCStatements resource returned",
+ "headers": {
+ "ETag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ }
+ },
+ "schema": {
+ "$ref": "pscModels.json#/definitions/statementList"
+ }
+ },
+ "401": {
+ "description": "Unauthorised"
+ },
+ "404": {
+ "description": "Resource not found"
+ }
+ }
+ }
+ },
+ "getPSCStatement": {
+ "get": {
+ "summary": "Get the person with significant control statement",
+ "description": "Get details of a person with significant control statement",
+ "x-operationName": "get statement",
+ "parameters": [
+ {
+ "name": "company_number",
+ "in": "path",
+ "description": "The company number of the persons with significant control statements list being requested.",
+ "required": true,
+ "type": "string"
+ },
+ {
+ "name": "statement_id",
+ "in": "path",
+ "description": "The id of the person with significant control statement details being requested.",
+ "required": true,
+ "type": "string"
+ }
+ ],
+ "tags": [
+ "personsWithSignificantControl"
+ ],
+ "responses": {
+ "200": {
+ "description": "PSCStatement resource returned",
+ "headers": {
+ "ETag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ }
+ },
+ "schema": {
+ "$ref": "pscModels.json#/definitions/statement"
+ }
+ },
+ "401": {
+ "description": "Unauthorised"
+ },
+ "404": {
+ "description": "Resource not found"
+ }
+ }
+ }
+ },
+ "getSuperSecurePSC": {
+ "get": {
+ "summary": "Get the super secure person with significant control",
+ "description": "Get details of a super secure person with significant control",
+ "x-operationName": "get super secure person",
+ "parameters": [
+ {
+ "name": "company_number",
+ "in": "path",
+ "description": "The company number of the super secure person with significant control details being requested.",
+ "required": true,
+ "type": "string"
+ },
+ {
+ "name": "super_secure_id",
+ "in": "path",
+ "description": "The id of the super secure person with significant control details being requested.",
+ "required": true,
+ "type": "string"
+ }
+ ],
+ "tags": [
+ "personsWithSignificantControl"
+ ],
+ "responses": {
+ "200": {
+ "description": "SuperSecurePSC resource returned",
+ "headers": {
+ "ETag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ }
+ },
+ "schema": {
+ "$ref": "pscModels.json#/definitions/superSecure"
+ }
+ },
+ "401": {
+ "description": "Unauthorised"
+ },
+ "404": {
+ "description": "Resource not found"
+ }
+ }
+ }
+ },
+ "getSuperSecureBO": {
+ "get": {
+ "summary": "Get the super secure beneficial owner",
+ "description": "Get details of a super secure beneficial owner",
+ "x-operationName": "get super secure beneficial owner",
+ "parameters": [
+ {
+ "name": "company_number",
+ "in": "path",
+ "description": "The company number of the super secure beneficial owner details being requested.",
+ "required": true,
+ "type": "string"
+ },
+ {
+ "name": "super_secure_id",
+ "in": "path",
+ "description": "The id of the super secure beneficial owner details being requested.",
+ "required": true,
+ "type": "string"
+ }
+ ],
+ "tags": [
+ "personsWithSignificantControl"
+ ],
+ "responses": {
+ "200": {
+ "description": "SuperSecureBO resource returned",
+ "headers": {
+ "ETag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ }
+ },
+ "schema": {
+ "$ref": "pscModels.json#/definitions/superSecureBeneficialOwner"
+ }
+ },
+ "401": {
+ "description": "Unauthorised"
+ },
+ "404": {
+ "description": "Resource not found"
+ }
+ }
+ }
+ }
+}
+
+
diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/pscModels.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/pscModels.json
new file mode 100644
index 0000000..51559eb
--- /dev/null
+++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/pscModels.json
@@ -0,0 +1,1598 @@
+{
+ "definitions": {
+ "address": {
+ "title": "pscAddress",
+ "required": [
+ "address_line_1",
+ "postal_code",
+ "premises"
+ ],
+ "properties": {
+ "address_line_1": {
+ "description": "The first line of the address.",
+ "type": "string"
+ },
+ "address_line_2": {
+ "description": "The second line of the address.",
+ "type": "string"
+ },
+ "care_of": {
+ "description": "Care of name.",
+ "type": "string"
+ },
+ "country": {
+ "description": "The country. For example, UK.",
+ "type": "string"
+ },
+ "locality": {
+ "description": "The locality. For example London.",
+ "type": "string"
+ },
+ "po_box": {
+ "description": "The post-officer box number.",
+ "type": "string"
+ },
+ "postal_code": {
+ "description": "The postal code. For example CF14 3UZ.",
+ "type": "string"
+ },
+ "premises": {
+ "description": "The property name or number.",
+ "type": "string"
+ },
+ "region": {
+ "description": "The region. For example Surrey.",
+ "type": "string"
+ }
+ }
+ },
+ "beneficialOwnerAddress": {
+ "title": "beneficialOwnerAddress",
+ "properties": {
+ "address_line_1": {
+ "description": "The first line of the address.",
+ "type": "string"
+ },
+ "address_line_2": {
+ "description": "The second line of the address.",
+ "type": "string"
+ },
+ "care_of": {
+ "description": "The care of name.",
+ "type": "string"
+ },
+ "country": {
+ "description": "The country. For example, United Kingdom.",
+ "type": "string"
+ },
+ "locality": {
+ "description": "The locality. For example London.",
+ "type": "string"
+ },
+ "po_box": {
+ "description": "The post-officer box number.",
+ "type": "string"
+ },
+ "postal_code": {
+ "description": "The postal code. For example CF14 3UZ.",
+ "type": "string"
+ },
+ "premises": {
+ "description": "The property name or number.",
+ "type": "string"
+ },
+ "region": {
+ "description": "The region. For example Surrey.",
+ "type": "string"
+ }
+ }
+ },
+ "list": {
+ "title": "list",
+ "properties": {
+ "items_per_page": {
+ "description": "The number of persons with significant control to return per page.",
+ "type": "integer"
+ },
+ "items": {
+ "description": "The list of persons with significant control.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/listTwoSummary"
+ },
+ "type": "array"
+ },
+ "start_index": {
+ "description": "The offset into the entire result set that this page starts.",
+ "type": "integer"
+ },
+ "total_results": {
+ "description": "The total number of persons with significant control in this result set.",
+ "type": "integer"
+ },
+ "active_count": {
+ "description": "The number of active persons with significant control in this result set.",
+ "type": "integer"
+ },
+ "ceased_count": {
+ "description": "The number of ceased persons with significant control in this result set.",
+ "type": "integer"
+ },
+ "links": {
+ "description": "A set of URLs related to the resource, including self.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/pscListLinksType"
+ },
+ "type": "object"
+ }
+ },
+ "required": [
+ "items_per_page",
+ "items",
+ "start_index",
+ "total_results",
+ "active_count",
+ "ceased_count",
+ "links"
+ ]
+ },
+ "individualList": {
+ "title": "individualList",
+ "properties": {
+ "etag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ },
+ "items_per_page": {
+ "description": "The number of individual persons with significant control to return per page.",
+ "type": "integer"
+ },
+ "kind": {
+ "enum": [
+ "persons-with-significant-control#list-individual"
+ ],
+ "type": "string"
+ },
+ "items": {
+ "description": "The list of individual persons with significant control.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/listSummary"
+ },
+ "type": "object"
+ },
+ "start_index": {
+ "description": "The offset into the entire result set that this page starts.",
+ "type": "integer"
+ },
+ "total_results": {
+ "description": "The total number of individual persons with significant control in this result set.",
+ "type": "integer"
+ },
+ "active_count": {
+ "description": "The number of active persons with significant control in this result set.",
+ "type": "integer"
+ },
+ "ceased_count": {
+ "description": "The number of ceased persons with significant control in this result set.",
+ "type": "integer"
+ },
+ "links": {
+ "description": "A set of URLs related to the resource, including self.",
+ "type": "object",
+ "items": {
+ "$ref": "pscModels.json#/definitions/pscListLinksType"
+ }
+ }
+ },
+ "required": [
+ "etag",
+ "items_per_page",
+ "kind",
+ "items",
+ "start_index",
+ "total_results",
+ "active_count",
+ "ceased_count",
+ "links"
+ ]
+ },
+ "corporateEntityList": {
+ "title": "corporateEntityList",
+ "properties": {
+ "etag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ },
+ "items_per_page": {
+ "description": "The number of corporate entity persons with significant control to return per page.",
+ "type": "integer"
+ },
+ "kind": {
+ "enum": [
+ "persons-with-significant-control#list-corporate-entity"
+ ],
+ "type": "string"
+ },
+ "items": {
+ "description": "The list of corporate entity persons with significant control.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/listSummary"
+ },
+ "type": "object"
+ },
+ "start_index": {
+ "description": "The offset into the entire result set that this page starts.",
+ "type": "integer"
+ },
+ "total_results": {
+ "description": "The total number of corporate entity persons with significant control in this result set.",
+ "type": "integer"
+ },
+ "active_count": {
+ "description": "The number of active persons with significant control in this result set.",
+ "type": "integer"
+ },
+ "ceased_count": {
+ "description": "The number of ceased persons with significant control in this result set.",
+ "type": "integer"
+ },
+ "links": {
+ "description": "A set of URLs related to the resource, including self.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/pscListLinksType"
+ },
+ "type": "object"
+ }
+ },
+ "required": [
+ "etag",
+ "items_per_page",
+ "kind",
+ "items",
+ "start_index",
+ "total_results",
+ "active_count",
+ "ceased_count",
+ "links"
+ ]
+ },
+ "legalPersonList": {
+ "title": "legalPersonList",
+ "properties": {
+ "etag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ },
+ "items_per_page": {
+ "description": "The number of legal persons with significant control to return per page.",
+ "type": "integer"
+ },
+ "kind": {
+ "enum": [
+ "persons-with-significant-control#list-legal-person"
+ ],
+ "type": "string"
+ },
+ "items": {
+ "description": "The list of legal persons with significant control.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/listSummary"
+ },
+ "type": "object"
+ },
+ "start_index": {
+ "description": "The offset into the entire result set that this page starts.",
+ "type": "integer"
+ },
+ "total_results": {
+ "description": "The total number of legal persons with significant control in this result set.",
+ "type": "integer"
+ },
+ "active_count": {
+ "description": "The number of active persons with significant control in this result set.",
+ "type": "integer"
+ },
+ "ceased_count": {
+ "description": "The number of ceased persons with significant control in this result set.",
+ "type": "integer"
+ },
+ "links": {
+ "description": "A set of URLs related to the resource, including self.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/pscListLinksType"
+ },
+ "type": "object"
+ }
+ },
+ "required": [
+ "etag",
+ "items_per_page",
+ "kind",
+ "items",
+ "start_index",
+ "total_results",
+ "active_count",
+ "ceased_count",
+ "links"
+ ]
+ },
+ "pscLinksType": {
+ "title": "pscLinksType",
+ "required": [
+ "self"
+ ],
+ "properties": {
+ "self": {
+ "description": "The URL of the resource.",
+ "type": "string"
+ },
+ "statement": {
+ "description": "The URL of the statement linked to this person with significant control.",
+ "type": "string"
+ },
+ "persons_with_significant_control": {
+ "description": "Links to other persons with significant control resources associated with this person with significant control.",
+ "type": "object",
+ "properties": {
+ "notifications": {
+ "description": "Link to the persons with significant control notification resource that this notification is associated with.",
+ "type": "string"
+ }
+ }
+ }
+ }
+ },
+ "pscItemsListLinksType": {
+ "title": "pscLinksType",
+ "required": [
+ "self"
+ ],
+ "properties": {
+ "self": {
+ "description": "The URL of the resource.",
+ "type": "string"
+ },
+ "statement": {
+ "description": "The URL of the statement linked to this person with significant control.",
+ "type": "string"
+ },
+ "persons_with_significant_control": {
+ "description": "Links to other persons with significant control resources associated with this person with significant control list item.",
+ "type": "object",
+ "properties": {
+ "notifications": {
+ "description": "Link to the persons with significant control notification resource that this notification is associated with.",
+ "type": "string"
+ }
+ }
+ }
+ }
+ },
+ "beneficialOwnerLinksType": {
+ "title": "beneficialOwnerLinksType",
+ "required": [
+ "self"
+ ],
+ "properties": {
+ "persons_with_significant_control": {
+ "description": "Links to other persons with significant control resources associated with this person with significant control.",
+ "type": "object",
+ "properties": {
+ "notifications": {
+ "description": "Link to the persons with significant control notification resource that this notification is associated with.",
+ "type": "string"
+ }
+ }
+ },
+ "self": {
+ "description": "The URL of the resource.",
+ "type": "string"
+ },
+ "statement": {
+ "description": "The URL of the statement linked to this beneficial owner.",
+ "type": "string"
+ }
+ }
+ },
+ "pscListLinksType": {
+ "title": "pscListLinksType",
+ "required": [
+ "self"
+ ],
+ "properties": {
+ "self": {
+ "description": "The URL of the resource.",
+ "type": "string"
+ },
+ "persons_with_significant_control_list": {
+ "description": "The URL of the person with significant control list resource.",
+ "notifications": "string",
+ "type": "string"
+ }
+ }
+ },
+ "statementListLinksType": {
+ "title": "statementListLinksType",
+ "required": [
+ "self"
+ ],
+ "properties": {
+ "self": {
+ "description": "The URL of the resource.",
+ "type": "string"
+ },
+ "persons_with_significant_control_statements_list": {
+ "description": "The URL of the persons with significant control statements list resource.",
+ "type": "string"
+ }
+ }
+ },
+ "statementLinksType": {
+ "title": "statementLinksType",
+ "required": [
+ "self"
+ ],
+ "properties": {
+ "self": {
+ "description": "The URL of the resource.",
+ "type": "string"
+ },
+ "person_with_significant_control": {
+ "description": "The URL of the person with significant control linked to this statement.",
+ "type": "string"
+ }
+ }
+ },
+ "superSecureLinksType": {
+ "title": "superSecureLinksType",
+ "required": [
+ "self"
+ ],
+ "properties": {
+ "self": {
+ "description": "The URL of the resource.",
+ "type": "string"
+ }
+ }
+ },
+ "listSummary": {
+ "title": "listSummary",
+ "required": [
+ "etag",
+ "name",
+ "links",
+ "address",
+ "notified_on",
+ "natures_of_control"
+ ],
+ "properties": {
+ "etag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ },
+ "notified_on": {
+ "description": "The date that Companies House was notified about this person with significant control.",
+ "type": "string",
+ "format": "date"
+ },
+ "ceased_on": {
+ "description": "The date that Companies House was notified about the cessation of this person with significant control.",
+ "type": "string",
+ "format": "date"
+ },
+ "country_of_residence": {
+ "description": "The country of residence of the person with significant control.",
+ "type": "string"
+ },
+ "date_of_birth": {
+ "description": "The date of birth of the person with significant control.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/dateOfBirthPSCList"
+ },
+ "type": "object"
+ },
+ "name": {
+ "description": "Name of the person with significant control.",
+ "type": "string"
+ },
+ "name_elements": {
+ "description": "A document encapsulating the separate elements of a person with significant control's name.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/nameElements"
+ },
+ "type": "object"
+ },
+ "links": {
+ "description": "A set of URLs related to the resource, including self.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/pscLinksType"
+ },
+ "type": "object"
+ },
+ "nationality": {
+ "description": "The nationality of the person with significant control.",
+ "type": "string"
+ },
+ "identification": {
+ "description": "",
+ "items": {
+ "$ref": "pscModels.json#/definitions/pscListIdent"
+ },
+ "type": "object"
+ },
+ "identity_verification_details": {
+ "description": "Information relating to the identity verification of the person with significant control",
+ "items": {
+ "$ref": "pscModels.json#/definitions/identityVerificationDetails"
+ },
+ "type": "object"
+ },
+ "ceased": {
+ "description" : "Presence of that indicator means the super secure person status is ceased
",
+ "type": "boolean"
+ },
+ "description": {
+ "description" : "Description of the super secure legal statement
",
+ "enum": [
+ "super-secure-persons-with-significant-control"
+ ],
+ "type": "string"
+ },
+ "kind": {
+ "enum": [
+ "individual-person-with-significant-control",
+ "corporate-entity-person-with-significant-control",
+ "legal-person-with-significant-control",
+ "super-secure-person-with-significant-control",
+ "individual-beneficial-owner",
+ "corporate-entity-beneficial-owner",
+ "legal-person-beneficial-owner",
+ "super-secure-beneficial-owner"
+ ],
+ "type": "string"
+ },
+ "address": {
+ "description": "The service address of the person with significant control. If given, this address will be shown on the public record instead of the residential address.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/address"
+ },
+ "type": "object"
+ },
+ "natures_of_control": {
+ "description": "Indicates the nature of control the person with significant control holds.\n For enumeration descriptions see `description` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/psc_descriptions.yml) file. \n",
+ "items": {
+ "type" : "string"
+ },
+ "type": "array"
+ },
+ "is_sanctioned": {
+ "description": "Flag indicating if the beneficial owner was declared as being sanctioned on the latest filing of the overseas entity",
+ "type": "boolean"
+ },
+ "principal_office_address": {
+ "description": "The principal/registered office address of a corporate-entity-beneficial-owner or legal-person-beneficial-owner of a registered-overseas-entity.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/beneficialOwnerAddress"
+ },
+ "type": "object"
+ }
+ }
+ },
+ "listTwoSummary": {
+ "title": "listSummary",
+ "required": [
+ "etag",
+ "name",
+ "links",
+ "address",
+ "notified_on",
+ "natures_of_control"
+ ],
+ "properties": {
+ "etag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ },
+ "notified_on": {
+ "description": "The date that Companies House was notified about this person with significant control.",
+ "type": "string",
+ "format": "date"
+ },
+ "ceased_on": {
+ "description": "The date that Companies House was notified about the cessation of this person with significant control.",
+ "type": "string",
+ "format": "date"
+ },
+ "country_of_residence": {
+ "description": "The country of residence of the person with significant control.",
+ "type": "string"
+ },
+ "date_of_birth": {
+ "description": "The date of birth of the person with significant control.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/dateOfBirthPSCList"
+ },
+ "type": "object"
+ },
+ "name": {
+ "description": "Name of the person with significant control.",
+ "type": "string"
+ },
+ "name_elements": {
+ "description": "A document encapsulating the separate elements of a person with significant control's name.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/nameElements"
+ },
+ "type": "object"
+ },
+ "links": {
+ "description": "A set of URLs related to the resource, including self.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/pscItemsListLinksType"
+ },
+ "type": "object"
+ },
+ "nationality": {
+ "description": "The nationality of the person with significant control.",
+ "type": "string"
+ },
+ "identification": {
+ "description": "",
+ "items": {
+ "$ref": "pscModels.json#/definitions/pscListIdent"
+ },
+ "type": "object"
+ },
+ "identity_verification_details": {
+ "description": "Information relating to the identity verification of the person with significant control",
+ "items": {
+ "$ref": "pscModels.json#/definitions/identityVerificationDetails"
+ },
+ "type": "object"
+ },
+ "ceased": {
+ "description" : "Presence of that indicator means the super secure person status is ceased
",
+ "type": "boolean"
+ },
+ "description": {
+ "description" : "Description of the super secure legal statement
",
+ "enum": [
+ "super-secure-persons-with-significant-control"
+ ],
+ "type": "string"
+ },
+ "kind": {
+ "enum": [
+ "individual-person-with-significant-control",
+ "corporate-entity-person-with-significant-control",
+ "legal-person-with-significant-control",
+ "super-secure-person-with-significant-control",
+ "individual-beneficial-owner",
+ "corporate-entity-beneficial-owner",
+ "legal-person-beneficial-owner",
+ "super-secure-beneficial-owner"
+ ],
+ "type": "string"
+ },
+ "address": {
+ "description": "The service address of the person with significant control. If given, this address will be shown on the public record instead of the residential address.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/address"
+ },
+ "type": "object"
+ },
+ "natures_of_control": {
+ "description": "Indicates the nature of control the person with significant control holds.\n For enumeration descriptions see `description` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/psc_descriptions.yml) file. \n",
+ "items": {
+ "type" : "string"
+ },
+ "type": "array"
+ },
+ "is_sanctioned": {
+ "description": "Flag indicating if the beneficial owner was declared as being sanctioned on the latest filing of the overseas entity",
+ "type": "boolean"
+ },
+ "principal_office_address": {
+ "description": "The principal/registered office address of a corporate-entity-beneficial-owner or legal-person-beneficial-owner of a registered-overseas-entity.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/beneficialOwnerAddress"
+ },
+ "type": "object"
+ }
+ }
+ },
+ "statementList": {
+ "title": "statementList",
+ "properties": {
+ "items_per_page": {
+ "description": "The number of persons with significant control statements to return per page.",
+ "type": "integer"
+ },
+ "items": {
+ "description": "The list of persons with significant control statements.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/statement"
+ },
+ "type": "object"
+ },
+ "start_index": {
+ "description": "The offset into the entire result set that this page starts.",
+ "type": "integer"
+ },
+ "total_results": {
+ "description": "The total number of persons with significant control statements in this result set.",
+ "type": "integer"
+ },
+ "active_count": {
+ "description": "The number of active persons with significant control statements in this result set.",
+ "type": "integer"
+ },
+ "ceased_count": {
+ "description": "The number of ceased persons with significant control statements in this result set.",
+ "type": "integer"
+ },
+ "links": {
+ "description": "A set of URLs related to the resource, including self.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/statementListLinksType"
+ },
+ "type": "object"
+ }
+ },
+ "required": [
+ "items_per_page",
+ "items",
+ "start_index",
+ "total_results",
+ "active_count",
+ "ceased_count",
+ "links"
+ ]
+ },
+ "statement": {
+ "title": "statement",
+ "required": [
+ "etag",
+ "kind",
+ "notified_on",
+ "statement",
+ "links"
+ ],
+ "properties": {
+ "etag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ },
+ "kind": {
+ "enum": [
+ "persons-with-significant-control-statement"
+ ],
+ "type": "string"
+ },
+ "notified_on": {
+ "description": "The date that the person with significant control statement was processed by Companies House.",
+ "type": "string",
+ "format": "date"
+ },
+ "ceased_on": {
+ "description": "The date that Companies House was notified about the cessation of this person with significant control.",
+ "type": "string",
+ "format": "date"
+ },
+ "restrictions_notice_withdrawal_reason": {
+ "description": "The reason for the company withdrawing a restrictions-notice-issued-to-psc statement",
+ "enum": [
+ "restrictions-notice-withdrawn-by-court-order",
+ "restrictions-notice-withdrawn-by-company",
+ "restrictions-notice-withdrawn-by-lp",
+ "restrictions-notice-withdrawn-by-court-order-lp",
+ "restrictions-notice-withdrawn-by-partnership",
+ "restrictions-notice-withdrawn-by-court-order-p"
+ ],
+ "type": "string"
+ },
+ "statement": {
+ "description": "Indicates the type of statement filed.\n For enumeration descriptions see `statement_description` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/psc_descriptions.yml) file. \n",
+ "type": "string"
+ },
+ "linked_psc_name": {
+ "description": "The name of the psc linked to this statement.",
+ "type": "string"
+ },
+ "links": {
+ "description": "A set of URLs related to the resource, including self.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/statementLinksType"
+ },
+ "type": "object"
+ }
+ }
+ },
+ "superSecure": {
+ "title": "superSecure",
+ "required": [
+ "etag",
+ "kind",
+ "description",
+ "links"
+ ],
+ "properties": {
+ "etag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ },
+ "kind": {
+ "enum": [
+ "super-secure-person-with-significant-control"
+ ],
+ "type": "string"
+ },
+ "description": {
+ "description": "Description of the super secure legal statement \n",
+ "enum": [
+ "super-secure-persons-with-significant-control"
+ ],
+ "type": "string"
+ },
+ "identity_verification_details": {
+ "description": "Information relating to the identity verification of the person with significant control",
+ "properties": {
+ "appointment_verification_end_on": {
+ "description": "The date on which the identity verification statement was removed for the notification",
+ "type": "string",
+ "format": "date"
+ },
+ "appointment_verification_start_on": {
+ "description": "The date on which the identity verification statement was supplied for the notification",
+ "type": "string",
+ "format": "date"
+ }
+ },
+ "type": "object"
+ },
+ "ceased": {
+ "description": "Presence of that indicator means the super secure person status is ceased \n",
+ "type": "boolean"
+ },
+ "links": {
+ "description": "A set of URLs related to the resource, including self.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/superSecureLinksType"
+ },
+ "type": "object"
+ }
+ }
+ },
+ "superSecureBeneficialOwner": {
+ "title": "superSecureBeneficialOwner",
+ "required": [
+ "etag",
+ "kind",
+ "description",
+ "links"
+ ],
+ "properties": {
+ "etag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ },
+ "kind": {
+ "enum": [
+ "super-secure-beneficial-owner"
+ ],
+ "type": "string"
+ },
+ "description": {
+ "description": "Description of the super secure legal statement \n",
+ "enum": [
+ "super-secure-beneficial-owner"
+ ],
+ "type": "string"
+ },
+ "ceased": {
+ "description": "Presence of this indicator means the super secure beneficial owner status is ceased \n",
+ "type": "boolean"
+ },
+ "links": {
+ "description": "A set of URLs related to the resource, including self.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/superSecureLinksType"
+ },
+ "type": "object"
+ }
+ }
+ },
+ "individual": {
+ "title": "individual",
+ "required": [
+ "etag",
+ "notified_on",
+ "kind",
+ "country_of_residence",
+ "date_of_birth",
+ "name",
+ "name_elements",
+ "links",
+ "nationality",
+ "address",
+ "natures_of_control"
+ ],
+ "properties": {
+ "etag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ },
+ "notified_on": {
+ "description": "The date that Companies House was notified about this person with significant control.",
+ "type": "string",
+ "format": "date"
+ },
+ "ceased_on": {
+ "description": "The date that Companies House was notified about the cessation of this person with significant control.",
+ "type": "string",
+ "format": "date"
+ },
+ "kind": {
+ "enum": [
+ "individual-person-with-significant-control"
+ ],
+ "type": "string"
+ },
+ "country_of_residence": {
+ "description": "The country of residence of the person with significant control.",
+ "type": "string"
+ },
+ "date_of_birth": {
+ "description": "The date of birth of the person with significant control.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/dateOfBirth"
+ },
+ "type": "object"
+ },
+ "name": {
+ "description": "Name of the person with significant control. Generated by combining the name elements.",
+ "type": "string"
+ },
+ "name_elements": {
+ "description": "A document encapsulating the separate elements of a person with significant control's name.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/nameElements"
+ },
+ "type": "object"
+ },
+ "links": {
+ "description": "A set of URLs related to the resource, including self.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/pscLinksType"
+ },
+ "type": "object"
+ },
+ "nationality": {
+ "description": "The nationality of the person with significant control.",
+ "type": "string"
+ },
+ "address": {
+ "description": "The service address of the person with significant control. If given, this address will be shown on the public record instead of the residential address.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/address"
+ },
+ "type": "object"
+ },
+ "natures_of_control": {
+ "description": "Indicates the nature of control the person with significant control holds.\n For enumeration descriptions see `description` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/psc_descriptions.yml) file. \n",
+ "type": "array"
+ },
+ "identity_verification_details": {
+ "description": "Information relating to the identity verification of the person with significant control",
+ "items": {
+ "$ref": "pscModels.json#/definitions/identityVerificationDetails"
+ },
+ "type": "object"
+ }
+ }
+ },
+ "individualBeneficialOwner": {
+ "title": "individualBeneficialOwner",
+ "required": [
+ "etag",
+ "kind",
+ "links"
+ ],
+ "properties": {
+ "etag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ },
+ "notified_on": {
+ "description": "The date that Companies House was notified about this beneficial owner.",
+ "type": "string",
+ "format": "date"
+ },
+ "ceased_on": {
+ "description": "The date that Companies House was notified about the cessation of this beneficial owner.",
+ "type": "string",
+ "format": "date"
+ },
+ "kind": {
+ "enum": [
+ "individual-beneficial-owner"
+ ],
+ "type": "string"
+ },
+ "date_of_birth": {
+ "description": "The date of birth of the beneficial owner.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/beneficialOwnerDateOfBirth"
+ },
+ "type": "object"
+ },
+ "name": {
+ "description": "Name of the beneficial owner. Generated by combining the name elements.",
+ "type": "string"
+ },
+ "name_elements": {
+ "description": "A document encapsulating the separate elements of a beneficial owner's name.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/beneficialOwnerNameElements"
+ },
+ "type": "object"
+ },
+ "links": {
+ "description": "A set of URLs related to the resource, including self.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/beneficialOwnerLinksType"
+ },
+ "type": "object"
+ },
+ "nationality": {
+ "description": "The nationality of the beneficial owner.",
+ "type": "string"
+ },
+ "address": {
+ "description": "The service address of the beneficial owner. If given, this address will be shown on the public record instead of the residential address.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/beneficialOwnerAddress"
+ },
+ "type": "object"
+ },
+ "natures_of_control": {
+ "description": "Indicates the nature of control the beneficial owner holds.\n For enumeration descriptions see `description` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/psc_descriptions.yml) file. \n",
+ "type": "array"
+ },
+ "is_sanctioned": {
+ "description": "Flag indicating if the beneficial owner was declared as being sanctioned on the latest filing of the overseas entity",
+ "type": "boolean"
+ }
+ }
+ },
+ "corporateEntity": {
+ "title": "corporateEntity",
+ "required": [
+ "etag",
+ "notified_on",
+ "kind",
+ "name",
+ "links",
+ "address",
+ "identification",
+ "natures_of_control"
+ ],
+ "properties": {
+ "etag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ },
+ "notified_on": {
+ "description": "The date that Companies House was notified about this person with significant control.",
+ "type": "string",
+ "format": "date"
+ },
+ "ceased_on": {
+ "description": "The date that Companies House was notified about the cessation of this person with significant control.",
+ "type": "string",
+ "format": "date"
+ },
+ "kind": {
+ "enum": [
+ "corporate-entity-person-with-significant-control"
+ ],
+ "type": "string"
+ },
+ "name": {
+ "description": "Name of the person with significant control.",
+ "type": "string"
+ },
+ "links": {
+ "description": "A set of URLs related to the resource, including self.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/pscLinksType"
+ },
+ "type": "object"
+ },
+ "address": {
+ "description": "The address of the person with significant control.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/address"
+ },
+ "type": "object"
+ },
+ "identification": {
+ "description": "",
+ "items": {
+ "$ref": "pscModels.json#/definitions/corporateEntityIdent"
+ },
+ "type": "object"
+ },
+ "natures_of_control": {
+ "description": "Indicates the nature of control the person with significant control holds.\n For enumeration descriptions see `description` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/psc_descriptions.yml) file. \n",
+ "type": "array"
+ }
+ }
+ },
+ "corporateEntityBeneficialOwner": {
+ "title": "corporateEntityBeneficialOwner",
+ "required": [
+ "etag",
+ "kind",
+ "links"
+ ],
+ "properties": {
+ "etag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ },
+ "notified_on": {
+ "description": "The date that Companies House was notified about this beneficial owner.",
+ "type": "string",
+ "format": "date"
+ },
+ "ceased_on": {
+ "description": "The date that Companies House was notified about the cessation of this beneficial owner.",
+ "type": "string",
+ "format": "date"
+ },
+ "kind": {
+ "enum": [
+ "corporate-entity-beneficial-owner"
+ ],
+ "type": "string"
+ },
+ "name": {
+ "description": "Name of the beneficial owner.",
+ "type": "string"
+ },
+ "links": {
+ "description": "A set of URLs related to the resource, including self.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/beneficialOwnerLinksType"
+ },
+ "type": "object"
+ },
+ "address": {
+ "description": "The address of the beneficial owner.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/beneficialOwnerAddress"
+ },
+ "type": "object"
+ },
+ "principal_office_address": {
+ "description": "The principal/registered office address of a corporate-entity-beneficial-owner of a registered-overseas-entity.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/beneficialOwnerAddress"
+ },
+ "type": "object"
+ },
+ "identification": {
+ "description": "",
+ "items": {
+ "$ref": "pscModels.json#/definitions/beneficialOwnerCorporateEntityIdent"
+ },
+ "type": "object"
+ },
+ "natures_of_control": {
+ "description": "Indicates the nature of control the beneficial owner holds.\n For enumeration descriptions see `description` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/psc_descriptions.yml) file. \n",
+ "type": "array"
+ },
+ "is_sanctioned": {
+ "description": "Flag indicating if the beneficial owner was declared as being sanctioned on the latest filing of the overseas entity",
+ "type": "boolean"
+ }
+ }
+ },
+ "legalPerson": {
+ "title": "legalPerson",
+ "required": [
+ "etag",
+ "notified_on",
+ "kind",
+ "name",
+ "links",
+ "address",
+ "identification",
+ "natures_of_control"
+ ],
+ "properties": {
+ "etag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ },
+ "notified_on": {
+ "description": "The date that Companies House was notified about this person with significant control.",
+ "type": "string",
+ "format": "date"
+ },
+ "ceased_on": {
+ "description": "The date that Companies House was notified about the cessation of this person with significant control.",
+ "type": "string",
+ "format": "date"
+ },
+ "kind": {
+ "enum": [
+ "legal-person-person-with-significant-control"
+ ],
+ "type": "string"
+ },
+ "name": {
+ "description": "Name of the person with significant control.",
+ "type": "string"
+ },
+ "links": {
+ "description": "A set of URLs related to the resource, including self.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/pscLinksType"
+ },
+ "type": "object"
+ },
+ "address": {
+ "description": "The address of the person with significant control.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/address"
+ },
+ "type": "object"
+ },
+ "identification": {
+ "description": "",
+ "items": {
+ "$ref": "pscModels.json#/definitions/legalPersonIdent"
+ },
+ "type": "object"
+ },
+ "natures_of_control": {
+ "description": "Indicates the nature of control the person with significant control holds.\n For enumeration descriptions see `description` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/psc_descriptions.yml) file. \n",
+ "type": "array"
+ }
+ }
+ },
+ "legalPersonBeneficialOwner": {
+ "title": "legalPersonBeneficialOwner",
+ "required": [
+ "etag",
+ "kind",
+ "links"
+ ],
+ "properties": {
+ "etag": {
+ "description": "The ETag of the resource.",
+ "type": "string"
+ },
+ "notified_on": {
+ "description": "The date that Companies House was notified about this beneficial owner.",
+ "type": "string",
+ "format": "date"
+ },
+ "ceased_on": {
+ "description": "The date that Companies House was notified about the cessation of this beneficial owner.",
+ "type": "string",
+ "format": "date"
+ },
+ "kind": {
+ "enum": [
+ "legal-person-beneficial-owner"
+ ],
+ "type": "string"
+ },
+ "name": {
+ "description": "Name of the beneficial owner.",
+ "type": "string"
+ },
+ "links": {
+ "description": "A set of URLs related to the resource, including self.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/beneficialOwnerLinksType"
+ },
+ "type": "object"
+ },
+ "address": {
+ "description": "The address of the beneficial owner.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/beneficialOwnerAddress"
+ },
+ "type": "object"
+ },
+ "principal_office_address": {
+ "description": "The principal/registered office address of a legal-person-beneficial-owner of a registered-overseas-entity.",
+ "items": {
+ "$ref": "pscModels.json#/definitions/beneficialOwnerAddress"
+ },
+ "type": "object"
+ },
+ "identification": {
+ "description": "",
+ "items": {
+ "$ref": "pscModels.json#/definitions/legalPersonBeneficialOwnerIdent"
+ },
+ "type": "object"
+ },
+ "natures_of_control": {
+ "description": "Indicates the nature of control the beneficial owner holds.\n For enumeration descriptions see `description` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/psc_descriptions.yml) file. \n",
+ "type": "array"
+ },
+ "is_sanctioned": {
+ "description": "Flag indicating if the beneficial owner was declared as being sanctioned on the latest filing of the overseas entity",
+ "type": "boolean"
+ }
+ }
+ },
+ "nameElements": {
+ "title": "nameElements",
+ "properties": {
+ "forename": {
+ "description": "The forename of the person with significant control.",
+ "type": "string"
+ },
+ "title": {
+ "description": "Title of the person with significant control.",
+ "type": "string"
+ },
+ "middle_name": {
+ "description": "The middle name of the person with significant control.",
+ "type": "string"
+ },
+ "surname": {
+ "description": "The surname of the person with significant control.",
+ "type": "string"
+ }
+ },
+ "required": [
+ "surname"
+ ]
+ },
+ "beneficialOwnerNameElements": {
+ "title": "beneficialOwnerNameElements",
+ "properties": {
+ "forename": {
+ "description": "The forename of the beneficial owner.",
+ "type": "string"
+ },
+ "title": {
+ "description": "Title of the beneficial owner.",
+ "type": "string"
+ },
+ "middle_name": {
+ "description": "The middle name of the beneficial owner.",
+ "type": "string"
+ },
+ "surname": {
+ "description": "The surname of the beneficial owner.",
+ "type": "string"
+ }
+ },
+ "required": [
+ "surname"
+ ]
+ },
+ "corporateEntityIdent": {
+ "title": "corporateEntityIdent",
+ "properties": {
+ "legal_authority": {
+ "description": "The legal authority supervising the corporate entity with significant control.",
+ "type": "string"
+ },
+ "legal_form": {
+ "description": "The legal form of the corporate entity with significant control as defined by its country of registration.",
+ "type": "string"
+ },
+ "place_registered": {
+ "description": "The place the corporate entity with significant control is registered.",
+ "type": "string"
+ },
+ "registration_number": {
+ "description": "The registration number of the corporate entity with significant control.",
+ "type": "string"
+ },
+ "country_registered": {
+ "description": "The country or state the corporate entity with significant control is registered in.",
+ "type": "string"
+ }
+ },
+ "required": [
+ "legal_authority",
+ "legal_form"
+ ]
+ },
+ "beneficialOwnerCorporateEntityIdent": {
+ "title": "beneficialOwnerCorporateEntityIdent",
+ "properties": {
+ "legal_authority": {
+ "description": "The legal authority supervising the corporate entity beneficial owner.",
+ "type": "string"
+ },
+ "legal_form": {
+ "description": "The legal form of the corporate entity beneficial owner as defined by its country of registration.",
+ "type": "string"
+ },
+ "place_registered": {
+ "description": "The place the corporate entity beneficial owner is registered.",
+ "type": "string"
+ },
+ "registration_number": {
+ "description": "The registration number of the corporate entity beneficial owner.",
+ "type": "string"
+ },
+ "country_registered": {
+ "description": "The country or state the corporate entity beneficial owner is registered in.",
+ "type": "string"
+ }
+ }
+ },
+ "pscListIdent": {
+ "title": "pscListIdent",
+ "properties": {
+ "legal_authority": {
+ "description": "The legal authority supervising the corporate entity or legal person with significant control.",
+ "type": "string"
+ },
+ "legal_form": {
+ "description": "The legal form of the corporate entity or legal person with significant control as defined by its country of registration.",
+ "type": "string"
+ },
+ "place_registered": {
+ "description": "The place the corporate entity with significant control is registered.",
+ "type": "string"
+ },
+ "registration_number": {
+ "description": "The registration number of the corporate entity with significant control.",
+ "type": "string"
+ },
+ "country_registered": {
+ "description": "The country or state the corporate entity with significant control is registered in.",
+ "type": "string"
+ }
+ },
+ "required": [
+ "legal_authority",
+ "legal_form"
+ ]
+ },
+ "identityVerificationDetails": {
+ "title": "identityVerificationDetails",
+ "properties": {
+ "anti_money_laundering_supervisory_bodies": {
+ "description": "The Anti-Money Laundering supervisory bodies that the authorised corporate service provider was registered with when verifying the person with significant control",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "appointment_verification_end_on": {
+ "description": "The date on which the identity verification statement was removed for the notification",
+ "type": "string",
+ "format": "date"
+ },
+ "appointment_verification_statement_date": {
+ "description": "The date from which an identity verification statement can be supplied for the notification",
+ "type": "string",
+ "format": "date"
+ },
+ "appointment_verification_statement_due_on": {
+ "description": "The date by which an identity verification statement must be supplied for the notification",
+ "type": "string",
+ "format": "date"
+ },
+ "appointment_verification_start_on": {
+ "description": "The date on which the identity verification statement was supplied for the notification",
+ "type": "string",
+ "format": "date"
+ },
+ "authorised_corporate_service_provider_name": {
+ "description": "The name of the authorised corporate service provider that verified the identity of the person with significant control",
+ "type": "string"
+ },
+ "identity_verified_on": {
+ "description": "The date on which the authorised corporate service provider verified the identity of the person with significant control",
+ "type": "string",
+ "format": "date"
+ },
+ "preferred_name": {
+ "description": "The name provided to the authorised corporate service provider by which the person with significant control prefers to be known",
+ "type": "string"
+ }
+ }
+ },
+ "legalPersonIdent": {
+ "title": "legalPersonIdent",
+ "properties": {
+ "legal_authority": {
+ "description": "The legal authority supervising the legal person with significant control.",
+ "type": "string"
+ },
+ "legal_form": {
+ "description": "The legal form of the legal person with significant control as defined by its country of registration.",
+ "type": "string"
+ }
+ },
+ "required": [
+ "legal_authority",
+ "legal_form"
+ ]
+ },
+ "legalPersonBeneficialOwnerIdent": {
+ "title": "legalPersonBeneficialOwnerIdent",
+ "properties": {
+ "legal_authority": {
+ "description": "The legal authority supervising the legal person beneficial owner.",
+ "type": "string"
+ },
+ "legal_form": {
+ "description": "The legal form of the legal person beneficial owner as defined by its country of registration.",
+ "type": "string"
+ }
+ }
+ },
+ "dateOfBirth": {
+ "title": "dateOfBirth",
+ "properties": {
+ "day": {
+ "description": "The day of the date of birth.",
+ "type": "integer"
+ },
+ "month": {
+ "description": "The month of date of birth.",
+ "type": "integer"
+ },
+ "year": {
+ "description": "The year of date of birth.",
+ "type": "integer"
+ }
+ },
+ "required": [
+ "month",
+ "year"
+ ]
+ },
+ "dateOfBirthPSCList": {
+ "title": "dateOfBirth",
+ "properties": {
+ "month": {
+ "description": "The month of date of birth.",
+ "type": "integer"
+ },
+ "year": {
+ "description": "The year of date of birth.",
+ "type": "integer"
+ }
+ },
+ "required": [
+ "month",
+ "year"
+ ]
+ },
+ "beneficialOwnerDateOfBirth": {
+ "title": "beneficialOwnerDateOfBirth",
+ "properties": {
+ "day": {
+ "description": "The day of the date of birth.",
+ "type": "integer"
+ },
+ "month": {
+ "description": "The month of date of birth.",
+ "type": "integer"
+ },
+ "year": {
+ "description": "The year of date of birth.",
+ "type": "integer"
+ }
+ }
+ }
+ }
+}
+
diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/pscNotificationList.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/pscNotificationList.json
new file mode 100644
index 0000000..cb46d60
--- /dev/null
+++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/pscNotificationList.json
@@ -0,0 +1,414 @@
+{
+ "get": {
+ "summary": "Persons with significant control Notification List",
+ "tags": [
+ "personsWithSignificantControlNotifications"
+ ],
+ "x-operationName": "list",
+ "description": "List of all notifications of a specific person with significant control",
+ "parameters": [
+ {
+ "name": "psc_id",
+ "in": "path",
+ "description": "The person with significant control id of the notification list being requested",
+ "required": true,
+ "type": "string"
+ },
+ {
+ "name": "filter",
+ "in": "query",
+ "description": "Use “active” to return only active notifications.",
+ "type": "string"
+ },
+ {
+ "name": "items_per_page",
+ "in": "query",
+ "description": "The number of notifications to return per page.",
+ "type": "integer"
+ },
+ {
+ "name": "start_index",
+ "in": "query",
+ "description": "The first row of data to retrieve, starting at 0. Use this parameter as a pagination mechanism along with the items_per_page parameter.",
+ "type": "integer"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "List the person with significant control notifications",
+ "schema": {
+ "$ref": "pscNotificationList.json#/definitions/notificationList"
+ }
+ },
+ "400": {
+ "description": "Bad request"
+ },
+ "401": {
+ "description": "Unauthorised"
+ }
+ }
+ },
+"definitions": {
+ "notificationList": {
+ "title": "notificationList",
+ "required": [
+ "active_count",
+ "ceased_count",
+ "inactive_count",
+ "items",
+ "items_per_page",
+ "links",
+ "name",
+ "start_index",
+ "total_results",
+ "kind"
+ ],
+ "properties": {
+ "active_count": {
+ "type": "integer",
+ "description": "The number of active persons with significant control notifications in this result set."
+ },
+ "ceased_count": {
+ "type": "integer",
+ "description": "The number of ceased persons with significant control notifications in this result set."
+ },
+ "date_of_birth": {
+ "type": "object",
+ "description": "The date of birth of the person with significant control.",
+ "required": [
+ "month",
+ "year"
+ ],
+ "properties": {
+ "month": {
+ "type": "integer",
+ "description": "The month of date of birth."
+ },
+ "year": {
+ "type": "integer",
+ "description": "The year of date of birth."
+ }
+ }
+ },
+ "inactive_count": {
+ "type": "integer",
+ "description": "The number of inactive persons with significant control notifications in this result set."
+ },
+ "items": {
+ "type": "array",
+ "description": "The list of person with significant control notifications.",
+ "items": {
+ "type": "object",
+ "required": [
+ "address",
+ "notified_to",
+ "name",
+ "etag",
+ "natures_of_control",
+ "notified_on",
+ "links"
+ ],
+ "properties": {
+ "address": {
+ "type": "object",
+ "description": "The service address of the person with significant control. If given, this address will be shown on the public record instead of the residential address.",
+ "required": [
+ "address_line_1",
+ "postal_code",
+ "premises"
+ ],
+ "properties": {
+ "address_line_1": {
+ "type": "string",
+ "description": "The first line of the address."
+ },
+ "address_line_2": {
+ "type": "string",
+ "description": "The second line of the address."
+ },
+ "care_of": {
+ "type": "string",
+ "description": "Care of name."
+ },
+ "country": {
+ "type": "string",
+ "description": "The country. For example, UK."
+ },
+ "locality": {
+ "type": "string",
+ "description": "The locality. For example London."
+ },
+ "po_box": {
+ "type": "string",
+ "description": "The post-office box number."
+ },
+ "postal_code": {
+ "type": "string",
+ "description": "The postal code. For example CF14 3UZ."
+ },
+ "premises": {
+ "type": "string",
+ "description": "The property name or number."
+ },
+ "region": {
+ "type": "string",
+ "description": "The region. For example Surrey."
+ }
+ }
+ },
+ "notified_to": {
+ "type": "object",
+ "description": "The company information of the notification.",
+ "required": [
+ "company_number"
+ ],
+ "properties": {
+ "company_name": {
+ "type": "string",
+ "description": "The name of the company the person with significant control is notified to."
+ },
+ "company_number": {
+ "type": "string",
+ "description": "The number of the company the person with significant control is notified to."
+ },
+ "company_status": {
+ "type": "string",
+ "description": "The status of the company the person with significant control is notified to."
+ }
+ }
+ },
+ "ceased_on": {
+ "type": "string",
+ "format": "date",
+ "description": "The date that Companies House was notified about the cessation of this person with significant control."
+ },
+ "country_of_residence": {
+ "type": "string",
+ "description": "The country of residence of the person with significant control."
+ },
+ "etag": {
+ "type": "string",
+ "description": "The ETag of the resource."
+ },
+ "identification": {
+ "type": "object",
+ "description": "Information related to the registration of either a `corporate-entity-person-with-significant-control` or a `corporate-entity-beneficial-owner`.",
+ "required": [
+ "legal_authority",
+ "legal_form"
+ ],
+ "properties": {
+ "country_registered": {
+ "type": "string",
+ "description": "The country or state the corporate entity with significant control is registered in."
+ },
+ "legal_authority": {
+ "type": "string",
+ "description": "The legal authority supervising the corporate entity or legal person with significant control."
+ },
+ "legal_form": {
+ "type": "string",
+ "description": "The legal form of the corporate entity or legal person with significant control as defined by its country of registration."
+ },
+ "place_registered": {
+ "type": "string",
+ "description": "The place the corporate entity with significant control is registered."
+ },
+ "registration_number": {
+ "type": "string",
+ "description": "The registration number of the corporate entity with significant control."
+ }
+ }
+ },
+ "identity_verification_details": {
+ "type": "object",
+ "description": "Information relating to the identity verification of the person with significant control.",
+ "properties": {
+ "anti_money_laundering_supervisory_bodies": {
+ "type": "array",
+ "description": "The Anti-Money Laundering supervisory bodies that the authorised corporate service provider was registered with when verifying the person with significant control",
+ "items": { "type": "string" }
+ },
+ "appointment_verification_end_on": {
+ "type": "string",
+ "format": "date",
+ "description": "The date on which the identity verification statement was removed for the notification."
+ },
+ "appointment_verification_start_on": {
+ "type": "string",
+ "format": "date",
+ "description": "The date on which the identity verification statement was supplied for the notification."
+ },
+ "appointment_verification_statement_date": {
+ "type": "string",
+ "format": "date",
+ "description": "The date from which an identity verification statement can be supplied for the notification."
+ },
+ "appointment_verification_statement_due_on": {
+ "type": "string",
+ "format": "date",
+ "description": "The date by which an identity verification statement must be supplied for the notification."
+ },
+ "authorised_corporate_service_provider_name": {
+ "type": "string",
+ "description": "The name of the authorised corporate service provider that verified the identity of the person with significant control."
+ },
+ "identity_verified_on": {
+ "type": "string",
+ "format": "date",
+ "description": "The date on which the authorised corporate service provider verified the identity of the person with significant control."
+ },
+ "preferred_name": {
+ "type": "string",
+ "description": "The name provided to the authorised corporate service provider by which the person with significant control prefers to be known."
+ }
+ }
+ },
+ "is_sanctioned": {
+ "type": "boolean",
+ "description": "Flag indicating if the beneficial owner was declared as being sanctioned on the latest filing of the overseas entity."
+ },
+ "kind": {
+ "type": "string",
+ "description": "Possible values are: - individual-person-with-significant-control
- corporate-entity-person-with-significant-control
- legal-person-with-significant-control
- individual-beneficial-owner
- corporate-entity-beneficial-owner
- legal-person-beneficial-owner
"
+ },
+ "links": {
+ "type": "object",
+ "description": "Links to other resources associated with this person with significant control notification resource.",
+ "required": [
+ "company"
+ ],
+ "properties": {
+ "company": {
+ "type": "string",
+ "description": "Link to the company profile resource that this notification is associated with."
+ }
+ }
+ },
+ "name": {
+ "type": "string",
+ "description": "Name of the person with significant control."
+ },
+ "name_elements": {
+ "type": "object",
+ "description": "A document encapsulating the separate elements of a person with significant control's name.",
+ "required": [
+ "surname"
+ ],
+ "properties": {
+ "forename": {
+ "type": "string",
+ "description": "The forename of the person with significant control."
+ },
+ "middle_name": {
+ "type": "string",
+ "description": "The middle name of the person with significant control."
+ },
+ "surname": {
+ "type": "string",
+ "description": "The surname of the person with significant control."
+ },
+ "title": {
+ "type": "string",
+ "description": "Title of the person with significant control."
+ }
+ }
+ },
+ "nationality": {
+ "type": "string",
+ "description": "The nationality of the person with significant control."
+ },
+ "natures_of_control": {
+ "type": "array",
+ "description": "Indicates the nature of control the person with significant control holds. For enumeration descriptions see `description` [section in the enumeration mappings file](https://github.com/companieshouse/api-enumerations/blob/master/psc_descriptions.yml).",
+ "items": {
+ "type": "string"
+ }
+ },
+ "notified_on": {
+ "type": "string",
+ "format": "date",
+ "description": "The date that Companies House was notified about this person with significant control."
+ },
+ "principal_office_address": {
+ "type": "object",
+ "description": "The principal/registered office address of a corporate-entity-beneficial-owner or legal-person-beneficial-owner of a registered-overseas-entity.",
+ "properties": {
+ "address_line_1": {
+ "type": "string",
+ "description": "The first line of the address."
+ },
+ "address_line_2": {
+ "type": "string",
+ "description": "The second line of the address."
+ },
+ "care_of": {
+ "type": "string",
+ "description": "The care of name."
+ },
+ "country": {
+ "type": "string",
+ "description": "The country. For example, United Kingdom."
+ },
+ "locality": {
+ "type": "string",
+ "description": "The locality. For example London."
+ },
+ "po_box": {
+ "type": "string",
+ "description": "The post-office box number."
+ },
+ "postal_code": {
+ "type": "string",
+ "description": "The postal code. For example CF14 3UZ."
+ },
+ "premises": {
+ "type": "string",
+ "description": "The property name or number."
+ },
+ "region": {
+ "type": "string",
+ "description": "The region. For example Surrey."
+ }
+ }
+ }
+ }
+ }
+ },
+ "items_per_page": {
+ "type": "integer",
+ "description": "The number of persons with significant control notifications to return per page."
+ },
+ "kind": {
+ "type": "string",
+ "description": "Possible values are: "
+ },
+ "links": {
+ "type": "object",
+ "description": "Links to other resources associated with this person with significant control notification resource.",
+ "required": [
+ "self"
+ ],
+ "properties": {
+ "self": {
+ "type": "string",
+ "description": "Link to this person with significant control notification resource."
+ }
+ }
+ },
+ "name": {
+ "type": "string",
+ "description": "The person with significant control name."
+ },
+ "start_index": {
+ "type": "integer",
+ "description": "The first row of data to retrieve, starting at 0. Use this parameter as a pagination mechanism along with the items_per_page parameter."
+ },
+ "total_results": {
+ "type": "integer",
+ "description": "The total number of persons with significant control notifications in this result set."
+ }
+ }
+ }
+ }
+}
diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/search-companies.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/search-companies.json
new file mode 100644
index 0000000..032df8b
--- /dev/null
+++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/search-companies.json
@@ -0,0 +1,688 @@
+{
+ "searchDissolved":{
+ "get":{
+ "summary":"Search for a dissolved company",
+ "description":"Search for a dissolved company",
+ "x-operationName": "search dissolved companies",
+ "tags": [
+ "search"
+ ],
+ "parameters":[
+ {
+ "name":"q",
+ "in":"query",
+ "description":"The company name being searched for",
+ "required":true,
+ "type":"string"
+ },
+ {
+ "name":"search_type",
+ "in":"query",
+ "description":"Determines type of search. Options are alphabetical, best-match, previous-name-dissolved",
+ "required":true,
+ "type":"string"
+ },
+ {
+ "name":"search_above",
+ "in":"query",
+ "description":"The ordered_alpha_key_with_id used for alphabetical paging",
+ "required":false,
+ "type":"string"
+ },
+ {
+ "name":"search_below",
+ "in":"query",
+ "description":"The ordered_alpha_key_with_id used for alphabetical paging",
+ "required":false,
+ "type":"string"
+ },
+ {
+ "name":"size",
+ "in":"query",
+ "description":"The maximum number of results matching the search term(s) to return with a range of 1 to 100",
+ "required":false,
+ "type":"string"
+ },
+ {
+ "name":"start_index",
+ "in":"query",
+ "description":"Used in best-match and previous-name-dissolved search-type",
+ "required":false,
+ "type":"string"
+ }
+ ],
+ "responses":{
+ "200":{
+ "description":"A list of dissolved companies",
+ "schema":{
+ "$ref": "search-companies.json#/definitions/dissolvedCompanySearch"
+ }
+ },
+ "404":{
+ "description":"No companies found"
+ },
+ "422":{
+ "description":"Invalid size parameter, size must be greater than zero and not greater than 100"
+ }
+ }
+ }
+ },
+ "searchAlphabetic":{
+ "get":{
+ "summary":"Search for a company",
+ "description":"Search for a company",
+ "x-operationName": "search companies alphabetically",
+ "tags": [
+ "search"
+ ],
+ "parameters":[
+ {
+ "name":"q",
+ "in":"query",
+ "description":"The company name being searched for",
+ "required":true,
+ "type":"string"
+ },
+ {
+ "name":"search_above",
+ "in":"query",
+ "description":"The ordered_alpha_key_with_id used for paging",
+ "required":false,
+ "type":"string"
+ },
+ {
+ "name":"search_below",
+ "in":"query",
+ "description":"The ordered_alpha_key_with_id used for paging",
+ "required":false,
+ "type":"string"
+ },
+ {
+ "name":"size",
+ "in":"query",
+ "description":"The maximum number of results matching the search term(s) to return with a range of 1 to 100",
+ "required":false,
+ "type":"string"
+ }
+ ],
+ "responses":{
+ "200":{
+ "description":"A list of companies",
+ "schema":{
+ "$ref": "search-companies.json#/definitions/alphabeticalCompanySearch"
+ }
+ },
+ "404":{
+ "description":"No companies found"
+ },
+ "422":{
+ "description":"Invalid size parameter, size must be greater than zero and not greater than 100"
+ }
+ }
+ }
+ },
+ "searchAdvanced":{
+ "get":{
+ "summary":"Advanced search for a company",
+ "description":"Advanced search for a company",
+ "x-operationName": "advanced company search",
+ "tags": [
+ "search"
+ ],
+ "parameters":[
+ {
+ "name":"company_name_includes",
+ "in":"query",
+ "description":"The company name includes advanced search filter",
+ "required":false,
+ "type":"string"
+ },
+ {
+ "name":"company_name_excludes",
+ "in":"query",
+ "description":"The company name excludes advanced search filter",
+ "required":false,
+ "type":"string"
+ },
+ {
+ "name":"company_status",
+ "in":"query",
+ "description":"The company status advanced search filter. To search using multiple values, use a comma delimited list or multiple of the same key i.e. company_status=xxx&company_status=yyy",
+ "required":false,
+ "type":"list"
+ },
+ {
+ "name":"company_subtype",
+ "in":"query",
+ "description":"The company subtype advanced search filter. To search using multiple values, use a comma delimited list or multiple of the same key i.e. company_subtype=xxx&company_subtype=yyy",
+ "required":false,
+ "type":"string"
+ },
+ {
+ "name":"company_type",
+ "in":"query",
+ "description":"The company type advanced search filter. To search using multiple values, use a comma delimited list or multiple of the same key i.e. company_type=xxx&company_type=yyy",
+ "required":false,
+ "type":"list"
+ },
+ {
+ "name":"dissolved_from",
+ "in":"query",
+ "description":"The dissolved from date advanced search filter",
+ "required":false,
+ "type":"date"
+ },
+ {
+ "name":"dissolved_to",
+ "in":"query",
+ "description":"The dissolved to date advanced search filter",
+ "required":false,
+ "type":"date"
+ },
+ {
+ "name":"incorporated_from",
+ "in":"query",
+ "description":"The incorporated from date advanced search filter",
+ "required":false,
+ "type":"date"
+ },
+ {
+ "name":"incorporated_to",
+ "in":"query",
+ "description":"The incorporated to date advanced search filter",
+ "required":false,
+ "type":"date"
+ },
+ {
+ "name":"location",
+ "in":"query",
+ "description":"The location advanced search filter",
+ "required":false,
+ "type":"string"
+ },
+ {
+ "name":"sic_codes",
+ "in":"query",
+ "description":"The SIC codes advanced search filter. To search using multiple values, use a comma delimited list or multiple of the same key i.e. sic_codes=xxx&sic_codes=yyy",
+ "required":false,
+ "type":"list"
+ },
+ {
+ "name":"size",
+ "in":"query",
+ "description":"The maximum number of results matching the search term(s) to return with a range of 1 to 5000",
+ "required":false,
+ "type":"string"
+ },
+ {
+ "name":"start_index",
+ "in":"query",
+ "description":"The point at which results will start from i.e show search results from result 20 (used for paging)",
+ "required":false,
+ "type":"string"
+ }
+ ],
+ "responses":{
+ "200":{
+ "description":"A list of companies",
+ "schema":{
+ "$ref": "search-companies.json#/definitions/advancedCompanySearch"
+ }
+ },
+ "400":{
+ "description":"Bad request"
+ },
+ "404":{
+ "description":"No companies found"
+ }
+ }
+ }
+ },
+ "definitions":{
+ "dissolvedCompanySearch":{
+ "title":"List of dissolved companies",
+ "type":"object",
+ "allOf":[
+ {
+ "properties":{
+ "etag":{
+ "type":"string"
+ },
+ "items":{
+ "type":"array",
+ "items":{
+ "$ref": "search-companies.json#/definitions/dissolvedCompany"
+ }
+ },
+ "kind":{
+ "type":"string",
+ "enum":[
+ "search#alphabetical-dissolved",
+ "search#dissolved",
+ "search#previous-name-dissolved"
+ ]
+ },
+ "top_hit":{
+ "allOf":[
+ {
+ "$ref": "search-companies.json#/definitions/dissolved_top_hit"
+ },
+ {
+ "description":"The best matching company in dissolved search results"
+ }
+ ]
+ },
+ "hits":{
+ "type":"string",
+ "description":"The number of hits returned on a best-match or previous-company-names search"
+ }
+ }
+ }
+ ]
+ },
+ "alphabeticalCompanySearch":{
+ "title":"List of companies",
+ "type":"object",
+ "allOf":[
+ {
+ "properties":{
+ "items":{
+ "type":"array",
+ "items":{
+ "$ref": "search-companies.json#/definitions/alphabeticalCompany"
+ }
+ },
+ "kind":{
+ "type":"string",
+ "enum":[
+ "search#alphabetical-search",
+ "search#enhanced-search"
+ ]
+ },
+ "top_hit":{
+ "allOf":[
+ {
+ "$ref": "search-companies.json#/definitions/alphabetical_top_hit"
+ },
+ {
+ "description":"The best matching company in alphabetical search results"
+ }
+ ]
+ }
+ }
+ }
+ ]
+ },
+ "advancedCompanySearch":{
+ "title":"A list of companies",
+ "type":"object",
+ "required":[
+ "top_hit",
+ "items",
+ "kind",
+ "hits"
+ ],
+ "allOf":[
+ {
+ "properties":{
+ "etag":{
+ "type":"string"
+ },
+ "items":{
+ "type":"array",
+ "items":{
+ "$ref": "search-companies.json#/definitions/advancedCompany"
+ }
+ },
+ "kind":{
+ "type":"string",
+ "enum":[
+ "search#advanced-search"
+ ]
+ },
+ "top_hit":{
+ "allOf":[
+ {
+ "$ref": "search-companies.json#/definitions/advanced_top_hit"
+ },
+ {
+ "description":"The best matching company in an advanced search results"
+ }
+ ]
+ },
+ "hits":{
+ "type":"string",
+ "description":"The number of matches found using advanced search"
+ }
+ }
+ }
+ ]
+ },
+ "dissolved_top_hit":{
+ "$ref": "search-companies.json#/definitions/dissolvedCompany"
+ },
+ "alphabetical_top_hit":{
+ "$ref": "search-companies.json#/definitions/alphabeticalCompany"
+ },
+ "advanced_top_hit":{
+ "$ref": "search-companies.json#/definitions/advancedCompany"
+ },
+ "dissolvedCompany":{
+ "title":"Dissolved company",
+ "required":[
+ "company_name",
+ "company_number",
+ "date_of_cessation",
+ "date_of_creation"
+ ],
+ "properties":{
+ "company_name":{
+ "type":"string",
+ "description":"The company name associated with the dissolved company"
+ },
+ "company_number":{
+ "type":"string",
+ "description":"The company number of the dissolved company"
+ },
+ "company_status":{
+ "type":"string",
+ "description":"The status of the company"
+ },
+ "ordered_alpha_key_with_id":{
+ "type":"string",
+ "description":"The alphakey with it's id associated with the dissolved company"
+ },
+ "kind":{
+ "type":"string",
+ "enum":[
+ "search-results#dissolved-company"
+ ],
+ "description":"The type of search result"
+ },
+ "date_of_cessation":{
+ "type":"string",
+ "format":"date",
+ "description":"The date that the company was dissolved"
+ },
+ "date_of_creation":{
+ "type":"string",
+ "format":"date",
+ "description":"The date that the company was incorporated"
+ },
+ "registered_office_address":{
+ "$ref": "search-companies.json#/definitions/dissolved_company_registered_office_address"
+ },
+ "previous_company_names":{
+ "type":"array",
+ "items":{
+ "$ref": "search-companies.json#/definitions/previous_company_name"
+ }
+ },
+ "matched_previous_company_name":{
+ "$ref": "search-companies.json#/definitions/previous_company_name"
+ }
+ }
+ },
+ "alphabeticalCompany":{
+ "title":"Alphabetical company",
+ "required":[
+ "company_name",
+ "company_number",
+ "company_status",
+ "company_type",
+ "links"
+ ],
+ "properties":{
+ "company_name":{
+ "type":"string",
+ "description":"The company name associated with the company"
+ },
+ "company_number":{
+ "type":"string",
+ "description":"The company number of the company"
+ },
+ "company_status":{
+ "type":"string",
+ "description":"The status of the company"
+ },
+ "ordered_alpha_key_with_id":{
+ "type":"string",
+ "description":"The alphakey with it's id associated with the company"
+ },
+ "kind":{
+ "type":"string",
+ "enum":[
+ "search-results#alphabetical-search"
+ ],
+ "description":"The type of search result"
+ },
+ "links":{
+ "type":"object",
+ "description":"The link to the company",
+ "properties":{
+ "company_profile":{
+ "type":"string",
+ "description":"The link to the company"
+ }
+ }
+ },
+ "company_type":{
+ "type":"string",
+ "description":"The type of company associated with the company"
+ }
+ }
+ },
+ "advanced_company_registered_office_address":{
+ "title":"Registered Office Address",
+ "description": "This will only appear if there are ROA details in the company record",
+ "properties":{
+ "address_line_1":{
+ "type":"string",
+ "description":"The first line of the address e.g Crown Way"
+ },
+ "address_line_2":{
+ "type":"string",
+ "description":"The second line of the address"
+ },
+ "locality":{
+ "type":"string",
+ "description":"The town associated to the ROA e.g Cardiff"
+ },
+ "postal_code":{
+ "type":"string",
+ "description":"The postal code e.g CF14 3UZ"
+ },
+ "region": {
+ "description": "The region e.g Surrey.",
+ "type": "string"
+ },
+ "country": {
+ "description": "The country.",
+ "enum": [
+ "Wales",
+ "England",
+ "Scotland",
+ "Great Britain",
+ "Not specified",
+ "United Kingdom",
+ "Northern Ireland"
+ ],
+ "type": "string"
+ }
+ }
+ },
+ "dissolved_company_registered_office_address":{
+ "title":"Registered Office Address",
+ "description": "This will only appear if there are ROA details in the company record",
+ "properties": {
+ "address_line_1": {
+ "type": "string",
+ "description": "The first line of the address e.g Crown Way"
+ },
+ "address_line_2": {
+ "type": "string",
+ "description": "The second line of the address"
+ },
+ "locality": {
+ "type": "string",
+ "description": "The town associated to the ROA e.g Cardiff"
+ },
+ "postal_code": {
+ "type": "string",
+ "description": "The postal code e.g CF14 3UZ"
+ }
+ }
+ },
+ "previous_company_name":{
+ "title":"Previous company name",
+ "properties":{
+ "company_number":{
+ "type":"string",
+ "description":"The company number of the dissolved company"
+ },
+ "ceased_on":{
+ "type":"string",
+ "format":"date",
+ "description":"The date that the company ceased being known under the company name"
+ },
+ "effective_from":{
+ "type":"string",
+ "format":"date",
+ "description":"The date that the company started being known under the company name"
+ },
+ "name":{
+ "type":"string",
+ "description":"The previous name of the company"
+ }
+ }
+ },
+ "advancedCompany":{
+ "title":"advancedCompany",
+ "required":[
+ "company_name",
+ "company_number",
+ "company_status",
+ "company_type",
+ "date_of_creation",
+ "kind"
+ ],
+ "properties":{
+ "company_name":{
+ "type":"string",
+ "description":"The company name associated with the company"
+ },
+ "company_number":{
+ "type":"string",
+ "description":"The company number of the company"
+ },
+ "company_status": {
+ "description": "The status of the company. \n For enumeration descriptions see `company_status` section in the [enumeration mappings] (https://github.com/companieshouse/api-enumerations/blob/master/constants.yml) ",
+ "type": "string",
+ "enum": [
+ "active",
+ "dissolved",
+ "open",
+ "closed",
+ "converted-closed",
+ "receivership",
+ "administration",
+ "liquidation",
+ "insolvency-proceedings",
+ "voluntary-arrangement",
+ "registered",
+ "removed"
+ ]
+ },
+ "company_type":{
+ "description": "The type of the company. \n For enumeration descriptions see `company_type` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/constants.yml) ",
+ "enum": [
+ "private-unlimited",
+ "ltd",
+ "plc",
+ "old-public-company",
+ "private-limited-guarant-nsc-limited-exemption",
+ "limited-partnership",
+ "private-limited-guarant-nsc",
+ "converted-or-closed",
+ "private-unlimited-nsc",
+ "private-limited-shares-section-30-exemption",
+ "protected-cell-company",
+ "assurance-company",
+ "oversea-company",
+ "eeig",
+ "icvc-securities",
+ "icvc-warrant",
+ "icvc-umbrella",
+ "registered-society-non-jurisdictional",
+ "industrial-and-provident-society",
+ "northern-ireland",
+ "northern-ireland-other",
+ "royal-charter",
+ "investment-company-with-variable-capital",
+ "unregistered-company",
+ "llp",
+ "other",
+ "european-public-limited-liability-company-se",
+ "uk-establishment",
+ "scottish-partnership",
+ "charitable-incorporated-organisation",
+ "scottish-charitable-incorporated-organisation",
+ "further-education-or-sixth-form-college-corporation",
+ "registered-overseas-entity"
+ ],
+ "type": "string"
+ },
+ "company_subtype":{
+ "description": "The subtype of the company. \n For enumeration descriptions see `company_subtype` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/constants.yml)",
+ "type":"string",
+ "enum":[
+ "community-interest-company",
+ "private-fund-limited-partnership"
+ ]
+ },
+ "kind":{
+ "type":"string",
+ "enum":[
+ "search-results#company"
+ ],
+ "description":"The type of search result"
+ },
+ "links":{
+ "type":"object",
+ "description":"The link to the company",
+ "properties":{
+ "company_profile":{
+ "type":"string",
+ "description":"The link to the company"
+ }
+ }
+ },
+ "date_of_cessation":{
+ "type":"string",
+ "format":"date",
+ "description":"The date that the company was dissolved"
+ },
+ "date_of_creation":{
+ "type":"string",
+ "format":"date",
+ "description":"The date that the company was incorporated"
+ },
+ "registered_office_address":{
+ "$ref": "search-companies.json#/definitions/advanced_company_registered_office_address"
+ },
+ "sic_codes":{
+ "items": {
+ "type": "string"
+ },
+ "type": "array",
+ "description":"SIC codes for this company"
+ }
+ }
+ }
+ },
+ "schemes":[
+ "https",
+ "http"
+ ]
+ }
+
diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/search.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/search.json
new file mode 100644
index 0000000..ea07db3
--- /dev/null
+++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/search.json
@@ -0,0 +1,986 @@
+{
+ "searchAll": {
+ "get": {
+ "summary": "Search All",
+ "description": "Search companies, officers and disqualified officers",
+ "x-operationName": "search all",
+ "tags": [
+ "search"
+ ],
+ "parameters": [
+ {
+ "name": "q",
+ "in": "query",
+ "description": "The term being searched for.",
+ "required": true,
+ "type": "string"
+ },
+ {
+ "name": "items_per_page",
+ "in": "query",
+ "description": "The number of search results to return per page.",
+ "required": false,
+ "type": "integer"
+ },
+ {
+ "name": "start_index",
+ "in": "query",
+ "description": "The index of the first result item to return.",
+ "required": false,
+ "type": "integer"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Search all",
+ "schema": {
+ "$ref": "search.json#/definitions/Search"
+ }
+ },
+ "401": {
+ "description": "Not authorised"
+ }
+ }
+ }
+ },
+ "searchCompanies": {
+ "get": {
+ "summary": "Search companies",
+ "description": "Search company information",
+ "x-operationName": "search companies",
+ "tags": [
+ "search"
+ ],
+ "parameters": [
+ {
+ "name": "q",
+ "in": "query",
+ "description": "The term being searched for.",
+ "required": true,
+ "type": "string"
+ },
+ {
+ "name": "items_per_page",
+ "in": "query",
+ "description": "The number of search results to return per page.",
+ "required": false,
+ "type": "integer"
+ },
+ {
+ "name": "start_index",
+ "in": "query",
+ "description": "The index of the first result item to return.",
+ "required": false,
+ "type": "integer"
+ },
+ {
+ "name":"restrictions",
+ "in":"query",
+ "description": "Enumerable options to restrict search results. Space separate multiple restriction options to combine functionality. For a \"company name availability\" search use \"active-companies legally-equivalent-company-name\" together.",
+ "required": false,
+ "type":"string"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Search company",
+ "schema": {
+ "$ref": "search.json#/definitions/CompanySearch"
+ }
+ },
+ "401": {
+ "description": "Not authorised"
+ }
+ }
+ }
+ },
+ "searchOfficers": {
+ "get": {
+ "summary": "Search company officers",
+ "description": "Search for officer information",
+ "x-operationName": "search officers",
+ "tags": [
+ "search"
+ ],
+ "parameters": [
+ {
+ "name": "q",
+ "in": "query",
+ "description": "The term being searched for.",
+ "required": true,
+ "type": "string"
+ },
+ {
+ "name": "items_per_page",
+ "in": "query",
+ "description": "The number of search results to return per page.",
+ "required": false,
+ "type": "integer"
+ },
+ {
+ "name": "start_index",
+ "in": "query",
+ "description": "The index of the first result item to return.",
+ "required": false,
+ "type": "integer"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Search officer",
+ "schema": {
+ "$ref": "search.json#/definitions/OfficerSearch"
+ }
+ },
+ "401": {
+ "description": "Not authorised"
+ }
+ }
+ }
+ },
+ "searchPersons-with-significant-control": {
+ "get": {
+ "summary": "Search company persons with significant control",
+ "description": "Search for persons with significant control information",
+ "x-operationName": "search persons with significant control",
+ "tags": [
+ "search"
+ ],
+ "parameters": [
+ {
+ "name": "q",
+ "in": "query",
+ "description": "The term being searched for.",
+ "required": true,
+ "type": "string"
+ },
+ {
+ "name": "items_per_page",
+ "in": "query",
+ "description": "The number of search results to return per page.",
+ "required": false,
+ "type": "integer"
+ },
+ {
+ "name": "start_index",
+ "in": "query",
+ "description": "The index of the first result item to return.",
+ "required": false,
+ "type": "integer"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Search persons with significant control",
+ "schema": {
+ "$ref": "search.json#/definitions/PersonsWithSignificantControlSearch"
+ }
+ },
+ "401": {
+ "description": "Not authorised"
+ }
+ }
+ }
+ },
+ "searchDisqualified-officers": {
+ "get": {
+ "summary": "Search disqualified officers",
+ "description": "Search for disqualified officer information",
+ "x-operationName": "search disqualified officers",
+ "tags": [
+ "search"
+ ],
+ "parameters": [
+ {
+ "name": "q",
+ "in": "query",
+ "description": "The term being searched for.",
+ "required": true,
+ "type": "string"
+ },
+ {
+ "name": "items_per_page",
+ "in": "query",
+ "description": "The number of search results to return per page.",
+ "required": false,
+ "type": "integer"
+ },
+ {
+ "name": "start_index",
+ "in": "query",
+ "description": "The index of the first result item to return.",
+ "required": false,
+ "type": "integer"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Search all",
+ "schema": {
+ "$ref": "search.json#/definitions/DisqualifiedOfficerSearch"
+ }
+ },
+ "401": {
+ "description": "Not authorised"
+ }
+ }
+ }
+ },
+ "definitions": {
+ "CommonSearch": {
+ "properties": {
+ "total_results": {
+ "type": "integer",
+ "description": "The number of further search results available for the current search."
+ },
+ "start_index": {
+ "type": "integer",
+ "description": "The index into the entire result set that this result page starts."
+ },
+ "items_per_page": {
+ "type": "integer",
+ "description": "The number of search items returned per page."
+ },
+ "etag": {
+ "type": "string",
+ "description": "The ETag of the resource"
+ }
+ }
+ },
+ "CommonSearchItems": {
+ "properties": {
+ "title": {
+ "type": "string",
+ "description": "The title of the search result."
+ },
+ "address_snippet": {
+ "type": "string",
+ "description": "A single line address. This will be the address that matched within the indexed document or the primary address otherwise (as returned by the `address` member)."
+ },
+ "links": {
+ "type": "object",
+ "description": "The URL of the search result.",
+ "items": {
+ "$ref": "search.json#/definitions/LinksModel"
+ }
+ },
+ "description": {
+ "type": "string",
+ "description": "The result description."
+ },
+ "snippet": {
+ "type": "string",
+ "description": "Summary information for the result showing additional details that have matched."
+ },
+ "matches": {
+ "type": "object",
+ "description": "A list of members and arrays of character offset defining substrings that matched the search terms.",
+ "items": {
+ "$ref": "search.json#/definitions/MatchesModel"
+ }
+ }
+ }
+ },
+ "CompanySearch": {
+ "title": "CompanySearch",
+ "allOf": [
+ {
+ "$ref": "search.json#/definitions/CommonSearch"
+ }
+ ],
+ "properties": {
+ "kind": {
+ "type": "string",
+ "description": "The type of search response returned.",
+ "enum": [
+ "search#companies"
+ ]
+ },
+ "items": {
+ "type": "array",
+ "description": "The results of the completed search.",
+ "items": {
+ "$ref": "search.json#/definitions/CompanySearchItems"
+ }
+ }
+ }
+ },
+ "Search": {
+ "title": "Search",
+ "allOf": [
+ {
+ "$ref": "search.json#/definitions/CommonSearch"
+ }
+ ],
+ "properties": {
+ "kind": {
+ "type": "string",
+ "description": "The type of search response returned.",
+ "enum": [
+ "search#all"
+ ]
+ },
+ "items": {
+ "type": "array",
+ "description": "The results of the completed search. See `items.kind` for details of each specific result resource returned.,",
+ "items": {
+ "$ref": "search.json#/definitions/SearchItems"
+ }
+ }
+ }
+ },
+ "CompanySearchItems": {
+ "title": "CompanySearchItems",
+ "allOf": [
+ {
+ "$ref": "search.json#/definitions/CommonSearchItems"
+ }
+ ],
+ "required": [
+ "kind",
+ "title",
+ "address_snippet",
+ "links",
+ "company_number",
+ "date_of_creation",
+ "company_type",
+ "company_status",
+ "address"
+ ],
+ "properties": {
+ "kind": {
+ "type": "string",
+ "description": "The type of search result.",
+ "enum": [
+ "searchresults#company"
+ ]
+ },
+ "description_identifier": {
+ "items": {
+ "type": "string",
+ "enum": [
+ "incorporated-on",
+ "registered-on",
+ "formed-on",
+ "dissolved-on",
+ "converted-closed-on",
+ "closed-on",
+ "closed",
+ "first-uk-establishment-opened-on",
+ "opened-on",
+ "voluntary-arrangement",
+ "receivership",
+ "insolvency-proceedings",
+ "liquidation",
+ "administration",
+ "registered",
+ "removed"
+ ]
+ },
+ "type": "array",
+ "description": "An array of enumeration types that make up the search description. See search_descriptions_raw.yaml in api-enumerations"
+ },
+ "company_number": {
+ "type": "string",
+ "description": "The company registration / incorporation number of the company."
+ },
+ "date_of_creation": {
+ "type": "string",
+ "format": "date",
+ "description": "The date the company was created."
+ },
+ "date_of_cessation": {
+ "type": "string",
+ "format": "date",
+ "description": "The date the company ended."
+ },
+ "company_type": {
+ "type": "string",
+ "enum": [
+ "private-unlimited",
+ "ltd",
+ "plc",
+ "old-public-company",
+ "private-limited-guarant-nsc-limited-exemption",
+ "limited-partnership",
+ "private-limited-guarant-nsc",
+ "converted-or-closed",
+ "private-unlimited-nsc",
+ "private-limited-shares-section-30-exemption",
+ "assurance-company",
+ "oversea-company",
+ "eeig",
+ "icvc-securities",
+ "icvc-warrant",
+ "icvc-umbrella",
+ "industrial-and-provident-society",
+ "northern-ireland",
+ "northern-ireland-other",
+ "royal-charter",
+ "investment-company-with-variable-capital",
+ "unregistered-company",
+ "llp",
+ "other",
+ "european-public-limited-liability-company-se",
+ "registered-overseas-entity"
+ ],
+ "description": "The company type."
+ },
+ "company_status": {
+ "type": "string",
+ "enum": [
+ "active",
+ "dissolved",
+ "liquidation",
+ "receivership",
+ "administration",
+ "voluntary-arrangement",
+ "converted-closed",
+ "insolvency-proceedings",
+ "registered",
+ "removed"
+ ],
+ "description": "The company status."
+ },
+ "address": {
+ "description": "The address of the company's registered office.",
+ "type": "object",
+ "items": {
+ "$ref": "search.json#/definitions/registeredOfficeAddress"
+ }
+ }
+ }
+ },
+ "SearchItems": {
+ "title": "SearchItems",
+ "allOf": [
+ {
+ "$ref": "search.json#/definitions/CommonSearchItems"
+ }
+ ],
+ "required": [
+ "kind",
+ "title",
+ "address_snippet",
+ "links",
+ "address"
+ ],
+ "properties": {
+ "kind": {
+ "type": "string",
+ "description": "The type of search result. Refer to the full resource descriptions [CompanySearch resource](api/docs/company/company_number/CompanySearch-resource.html) [OfficerSearch resource] (api/docs/company/company_number/OfficerSearch-resource.html) and [DisqualifiedOfficerSearch resource](api/docs/company/company_number/DisqualifiedOfficerSearch-resource.html) for the full list of members returned.",
+ "enum": [
+ "searchresults#company",
+ "searchresults#officer",
+ "searchresults#persons-with-significant-control",
+ "searchresults#disqualified-officer"
+ ]
+ },
+ "description_identifier": {
+ "items": {
+ "type": "string",
+ "enum": [
+ "incorporated-on",
+ "registered-on",
+ "formed-on",
+ "dissolved-on",
+ "converted-closed-on",
+ "closed-on",
+ "closed",
+ "first-uk-establishment-opened-on",
+ "opened-on",
+ "voluntary-arrangement",
+ "receivership",
+ "insolvency-proceedings",
+ "liquidation",
+ "administration",
+ "appointment-count",
+ "born-on",
+ "registered",
+ "removed"
+ ]
+ },
+ "type": "array",
+ "description": "An array of enumeration types that make up the search description. See search_descriptions_raw.yaml in api-enumerations"
+ },
+ "address": {
+ "description": "The address of the company's registered office.",
+ "type": "object",
+ "items": {
+ "$ref": "search.json#/definitions/registeredOfficeAddress"
+ }
+ }
+ }
+ },
+ "LinksModel": {
+ "title": "LinksModel",
+ "properties": {
+ "self": {
+ "type": "string",
+ "description": "The URL of the resource being returned by the search item."
+ }
+ }
+ },
+ "MatchesModel": {
+ "title": "MatchesModel",
+ "properties": {
+ "title": {
+ "items": {
+ "type": "integer"
+ },
+ "type": "array",
+ "description": "An array of character offset into the `title` string. These always occur in pairs and define the start and end of substrings in the member `title` that matched the search terms. The first character of the string is index 1."
+ },
+ "snippet": {
+ "items": {
+ "type": "integer"
+ },
+ "type": "array",
+ "description": "An array of character offset into the `snippet` string. These always occur in pairs and define the start and end of substrings in the member `snippet` that matched the search terms. The first character of the string is index 1."
+ },
+ "address_snippet": {
+ "items": {
+ "type": "integer"
+ },
+ "type": "array",
+ "description": "An array of character offset into the `address_snippet` string. These always occur in pairs and define the start and end of substrings in the member `address_snippet` that matched the search terms."
+ }
+ }
+ },
+ "registeredOfficeAddress": {
+ "title": "registeredOfficeAddress",
+ "required": [
+ "address_line_1"
+ ],
+ "properties": {
+ "address_line_1": {
+ "description": "The first line of the address.",
+ "type": "string"
+ },
+ "address_line_2": {
+ "description": "The second line of the address.",
+ "type": "string"
+ },
+ "country": {
+ "description": "The country.",
+ "enum": [
+ "Wales",
+ "England",
+ "Scotland",
+ "Great Britain",
+ "Not specified",
+ "United Kingdom",
+ "Northern Ireland"
+ ],
+ "type": "string"
+ },
+ "locality": {
+ "description": "The locality e.g London.",
+ "type": "string"
+ },
+ "po_box": {
+ "description": "The post-office box number.",
+ "type": "string"
+ },
+ "postal_code": {
+ "description": "The postal code e.g CF14 3UZ.",
+ "type": "string"
+ },
+ "care_of": {
+ "description": "The care of name.",
+ "type": "string"
+ },
+ "region": {
+ "description": "The region e.g Surrey.",
+ "type": "string"
+ }
+ }
+ },
+ "OfficerSearch": {
+ "title": "OfficerSearch",
+ "allOf": [
+ {
+ "$ref": "search.json#/definitions/CommonSearch"
+ }
+ ],
+ "required": [
+ "kind"
+ ],
+ "properties": {
+ "kind": {
+ "type": "string",
+ "description": "The type of response returned.",
+ "enum": [
+ "search#officers"
+ ]
+ },
+ "items": {
+ "type": "array",
+ "description": "The results of the completed search.",
+ "items": {
+ "$ref": "search.json#/definitions/OfficerSearchItems"
+ }
+ }
+ }
+ },
+ "OfficerDateOfBirth": {
+ "title": "OfficerDateOfBirth",
+ "required": [
+ "month",
+ "year"
+ ],
+ "properties": {
+ "month": {
+ "description": "The month the officer was born in.",
+ "type": "integer"
+ },
+ "year": {
+ "description": "The year the officer was born in.",
+ "type": "integer"
+ }
+ }
+ },
+ "OfficerSearchItems": {
+ "title": "OfficerSearchItems",
+ "allOf": [
+ {
+ "$ref": "search.json#/definitions/CommonSearchItems"
+ }
+ ],
+ "required": [
+ "appointment_count",
+ "description",
+ "kind",
+ "title",
+ "address_snippet",
+ "address"
+ ],
+ "properties": {
+ "kind": {
+ "type": "string",
+ "description": "Describes the type of result returned.",
+ "enum": [
+ "searchresults#officer"
+ ]
+ },
+ "date_of_birth": {
+ "description": "The officer date of birth details.",
+ "items": {
+ "$ref": "search.json#/definitions/OfficerDateOfBirth"
+ }
+ },
+ "appointment_count": {
+ "type": "integer",
+ "description": "The total number of appointments the officer has."
+ },
+ "description_identifiers": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": [
+ "appointment-count",
+ "born-on"
+ ]
+ },
+ "description": "An array of enumeration types that make up the search description. See search_descriptions_raw.yaml in api-enumerations."
+ },
+ "address": {
+ "type": "object",
+ "description": "The service address of the officer.",
+ "items": {
+ "$ref": "search.json#/definitions/OfficerAddress"
+ }
+ }
+ }
+ },
+ "OfficerAddress": {
+ "title": "OfficerAddress",
+ "properties": {
+ "address_line_1": {
+ "description": "The first line of the address.",
+ "type": "string"
+ },
+ "address_line_2": {
+ "description": "The second line of the address.",
+ "type": "string"
+ },
+ "care_of": {
+ "description": "The care of name.",
+ "type": "string"
+ },
+ "country": {
+ "description": "The country. For example UK.",
+ "type": "string"
+ },
+ "locality": {
+ "description": "The locality. For example London.",
+ "type": "string"
+ },
+ "po_box": {
+ "description": "The post-office box number.",
+ "type": "string"
+ },
+ "postal_code": {
+ "description": "The postal code. For example CF14 3UZ.",
+ "type": "string"
+ },
+ "premises": {
+ "description": "The property name or number.",
+ "type": "string"
+ },
+ "region": {
+ "description": "The region. For example Surrey.",
+ "type": "string"
+ }
+ }
+ },
+ "PersonsWithSignificantControlSearch": {
+ "title": "PersonswithsignificantcontrolSearch",
+ "allOf": [
+ {
+ "$ref": "search.json#/definitions/CommonSearch"
+ }
+ ],
+ "required": [
+ "kind"
+ ],
+ "properties": {
+ "kind": {
+ "type": "string",
+ "description": "The type of response returned.",
+ "enum": [
+ "search#persons-with-significant-control"
+ ]
+ },
+ "items": {
+ "type": "array",
+ "description": "The results of the completed search.",
+ "items": {
+ "$ref": "search.json#/definitions/PersonsWithSignificantControlSearchItems"
+ }
+ }
+ }
+ },
+ "PersonsWithSignificantControlDateOfBirth": {
+ "title": "PersonswithsignificantcontrolDateOfBirth",
+ "required": [
+ "month",
+ "year"
+ ],
+ "properties": {
+ "month": {
+ "description": "The month the person with significant control was born in.",
+ "type": "integer"
+ },
+ "year": {
+ "description": "The year the person with significant control was born in.",
+ "type": "integer"
+ }
+ }
+ },
+ "PersonsWithSignificantControlAddress": {
+ "title": "PersonswithsignificantcontrolAddress",
+ "properties": {
+ "address_line_1": {
+ "description": "The first line of the address.",
+ "type": "string"
+ },
+ "address_line_2": {
+ "description": "The second line of the address.",
+ "type": "string"
+ },
+ "care_of": {
+ "description": "The care of name.",
+ "type": "string"
+ },
+ "country": {
+ "description": "The country. For example UK.",
+ "type": "string"
+ },
+ "locality": {
+ "description": "The locality. For example London.",
+ "type": "string"
+ },
+ "po_box": {
+ "description": "The post-office box number.",
+ "type": "string"
+ },
+ "postal_code": {
+ "description": "The postal code. For example CF14 3UZ.",
+ "type": "string"
+ },
+ "premises": {
+ "description": "The property name or number.",
+ "type": "string"
+ },
+ "region": {
+ "description": "The region. For example Surrey.",
+ "type": "string"
+ }
+ }
+ },
+ "PersonsWithSignificantControlSearchItems": {
+ "title": "PersonswithsignificantcontrolSearchItems",
+ "allOf": [
+ {
+ "$ref": "search.json#/definitions/CommonSearchItems"
+ }
+ ],
+ "required": [
+ "notification_count",
+ "description",
+ "kind",
+ "title",
+ "address_snippet",
+ "address"
+ ],
+ "properties": {
+ "kind": {
+ "type": "string",
+ "description": "Describes the type of result returned.",
+ "enum": [
+ "searchresults#persons-with-significant-control"
+ ]
+ },
+ "notification_count": {
+ "type": "integer",
+ "description": "The total number of notifications the person with significant control has."
+ },
+ "date_of_birth": {
+ "description": "The person with significant control date of birth details.",
+ "items": {
+ "$ref": "search.json#/definitions/PersonsWithSignificantControlDateOfBirth"
+ }
+ },
+ "description_identifiers": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": [
+ "notification-count",
+ "born-on"
+ ]
+ },
+ "description": "An array of enumeration types that make up the search description. See search_descriptions_raw.yaml in api-enumerations."
+ },
+ "address": {
+ "type": "object",
+ "description": "The service address of the person with significant control.",
+ "items": {
+ "$ref": "search.json#/definitions/PersonsWithSignificantControlAddress"
+ }
+ }
+ }
+ },
+ "DisqualifiedOfficerSearch": {
+ "title": "DisqualifiedOfficerSearch",
+ "allOf": [
+ {
+ "$ref": "search.json#/definitions/CommonSearch"
+ }
+ ],
+ "required": [
+ "kind",
+ "total_results",
+ "start_index",
+ "items_per_page"
+ ],
+ "properties": {
+ "kind": {
+ "type": "string",
+ "description": "The type of response returned.",
+ "enum": [
+ "search#disqualified-officers"
+ ]
+ },
+ "items": {
+ "type": "array",
+ "description": "The results of the completed search.",
+ "items": {
+ "$ref": "search.json#/definitions/DisqualifiedOfficerSearchItems"
+ }
+ }
+ }
+ },
+ "DisqualifiedOfficerSearchItems": {
+ "title": "DisqualifiedOfficerSearchItems",
+ "allOf": [
+ {
+ "$ref": "search.json#/definitions/CommonSearchItems"
+ }
+ ],
+ "required": [
+ "kind",
+ "title",
+ "description",
+ "address",
+ "address_snippet"
+ ],
+ "properties": {
+ "kind": {
+ "type": "string",
+ "description": "Describes the type of result returned.",
+ "enum": [
+ "searchresults#disqualified-officer"
+ ]
+ },
+ "date_of_birth": {
+ "type": "string",
+ "format": "date",
+ "description": "The disqualified officer's date of birth."
+ },
+ "description_identifiers": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": [
+ "born-on"
+ ]
+ },
+ "description": "An array of enumeration types that make up the search description. See search_descriptions_raw.yaml in api-enumerations."
+ },
+ "address": {
+ "type": "object",
+ "description": "The address of the disqualified officer as provided by the disqualifying authority.",
+ "items": {
+ "$ref": "search.json#/definitions/DisqualifiedOfficerAddress"
+ }
+ }
+ }
+ },
+ "DisqualifiedOfficerAddress": {
+ "title": "DisqualifiedOfficerAddress",
+ "properties": {
+ "address_line_1": {
+ "description": "The first line of the address.",
+ "type": "string"
+ },
+ "address_line_2": {
+ "description": "The second line of the address.",
+ "type": "string"
+ },
+ "country": {
+ "description": "The country. For example UK.",
+ "type": "string"
+ },
+ "locality": {
+ "description": "The locality. For example London.",
+ "type": "string"
+ },
+ "postal_code": {
+ "description": "The postal code. For example CF14 3UZ.",
+ "type": "string"
+ },
+ "premises": {
+ "description": "The property name or number.",
+ "type": "string"
+ },
+ "region": {
+ "description": "The region. For example Surrey.",
+ "type": "string"
+ }
+ }
+ }
+ }
+}
+
diff --git a/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouse.Extensions.Microsoft.DependencyInjection.csproj b/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouse.Extensions.Microsoft.DependencyInjection.csproj
index 7faeb94..b60ae5b 100644
--- a/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouse.Extensions.Microsoft.DependencyInjection.csproj
+++ b/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouse.Extensions.Microsoft.DependencyInjection.csproj
@@ -1,29 +1,37 @@
- netstandard2.0
+ net8.0;net9.0;net10.0
true
snupkg
+ true
+ README.md
The CompaniesHouse extensions for ASP.NET Core
- Copyright © Kevsoft 2020
+
-
-
+
+
+
+
+
+
+
+
diff --git a/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseClientDocumentOptions.cs b/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseClientDocumentOptions.cs
index 3f6b6b7..a3e5643 100644
--- a/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseClientDocumentOptions.cs
+++ b/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseClientDocumentOptions.cs
@@ -1,10 +1,23 @@
using System;
+using System.ComponentModel.DataAnnotations;
namespace CompaniesHouse.Extensions.Microsoft.DependencyInjection
{
+ ///
+ /// Options used to configure the Companies House document client.
+ ///
public class CompaniesHouseClientDocumentOptions
{
+ ///
+ /// The base of the Companies House document API.
+ ///
+ [Required]
public Uri BaseUri { get; set; } = CompaniesHouseUris.DocumentApi;
- public string ApiKey { get; set; }
+
+ ///
+ /// The Companies House API key used to authenticate requests.
+ ///
+ [Required(AllowEmptyStrings = false)]
+ public string ApiKey { get; set; } = string.Empty;
}
}
\ No newline at end of file
diff --git a/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseClientOptions.cs b/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseClientOptions.cs
index 153f604..5b54cf1 100644
--- a/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseClientOptions.cs
+++ b/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseClientOptions.cs
@@ -1,10 +1,23 @@
using System;
+using System.ComponentModel.DataAnnotations;
namespace CompaniesHouse.Extensions.Microsoft.DependencyInjection
{
+ ///
+ /// Options used to configure the Companies House client.
+ ///
public class CompaniesHouseClientOptions
{
+ ///
+ /// The base of the Companies House public data API.
+ ///
+ [Required]
public Uri BaseUri { get; set; } = CompaniesHouseUris.Default;
- public string ApiKey { get; set; }
+
+ ///
+ /// The Companies House API key used to authenticate requests.
+ ///
+ [Required(AllowEmptyStrings = false)]
+ public string ApiKey { get; set; } = string.Empty;
}
}
\ No newline at end of file
diff --git a/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseClientServiceCollectionExtensions.cs b/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseClientServiceCollectionExtensions.cs
index f166c40..5633e79 100644
--- a/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseClientServiceCollectionExtensions.cs
+++ b/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseClientServiceCollectionExtensions.cs
@@ -2,7 +2,9 @@
using CompaniesHouse;
using CompaniesHouse.DelegatingHandlers;
using CompaniesHouse.Extensions.Microsoft.DependencyInjection;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Options;
namespace Microsoft.Extensions.DependencyInjection
{
@@ -11,6 +13,12 @@ namespace Microsoft.Extensions.DependencyInjection
///
public static class CompaniesHouseClientServiceCollectionExtensions
{
+ ///
+ /// The default configuration section name used when binding
+ /// from an .
+ ///
+ public const string DefaultSectionName = "CompaniesHouse";
+
///
/// Registers the companies house client
///
@@ -19,7 +27,7 @@ public static class CompaniesHouseClientServiceCollectionExtensions
/// Service collection
public static IServiceCollection AddCompaniesHouseClient(this IServiceCollection services, string apiKey)
{
- return services.AddCompaniesHouseClient(opt => { opt.ApiKey = apiKey; });
+ return services.AddCompaniesHouseClient(options => options.ApiKey = apiKey);
}
///
@@ -32,10 +40,10 @@ public static IServiceCollection AddCompaniesHouseClient(this IServiceCollection
public static IServiceCollection AddCompaniesHouseClient(this IServiceCollection services, Uri baseUri,
string apiKey)
{
- return services.AddCompaniesHouseClient(opt =>
+ return services.AddCompaniesHouseClient(options =>
{
- opt.BaseUri = baseUri;
- opt.ApiKey = apiKey;
+ options.BaseUri = baseUri;
+ options.ApiKey = apiKey;
});
}
@@ -44,11 +52,18 @@ public static IServiceCollection AddCompaniesHouseClient(this IServiceCollection
///
/// Service collection
/// Companies house client options configuration
+ /// Optional hook to customise the underlying , e.g. to add resilience handlers
/// Service collection
- private static IServiceCollection AddCompaniesHouseClient(this IServiceCollection services,
- Action configure)
+ public static IServiceCollection AddCompaniesHouseClient(this IServiceCollection services,
+ Action configure,
+ Action? configureHttpClientBuilder = null)
{
- return services.AddCompaniesHouseClient((provider, options) => configure(options));
+ services.AddOptions()
+ .Configure(configure)
+ .ValidateDataAnnotations()
+ .ValidateOnStart();
+
+ return services.AddCompaniesHouseClientCore(configureHttpClientBuilder);
}
///
@@ -56,60 +71,296 @@ private static IServiceCollection AddCompaniesHouseClient(this IServiceCollectio
///
/// Service collection
/// Companies house client options configuration
+ /// Optional hook to customise the underlying , e.g. to add resilience handlers
/// Service collection
public static IServiceCollection AddCompaniesHouseClient(this IServiceCollection services,
- Action configure)
+ Action configure,
+ Action? configureHttpClientBuilder = null)
{
- services.TryAddSingleton(provider =>
- {
- var options = new CompaniesHouseClientOptions();
- configure.Invoke(provider, options);
- return options;
- });
+ services.AddOptions()
+ .Configure((options, provider) => configure(provider, options))
+ .ValidateDataAnnotations()
+ .ValidateOnStart();
+
+ return services.AddCompaniesHouseClientCore(configureHttpClientBuilder);
+ }
+
+ ///
+ /// Registers the companies house client, binding from configuration.
+ ///
+ /// Service collection
+ /// The configuration to bind options from
+ /// The configuration section name (defaults to )
+ /// Optional hook to customise the underlying , e.g. to add resilience handlers
+ /// Service collection
+ public static IServiceCollection AddCompaniesHouseClient(this IServiceCollection services,
+ IConfiguration configuration, string sectionName = DefaultSectionName,
+ Action? configureHttpClientBuilder = null)
+ {
+ return services.AddCompaniesHouseClient(configuration.GetSection(sectionName), configureHttpClientBuilder);
+ }
+
+ ///
+ /// Registers the companies house client, binding from a configuration section.
+ ///
+ /// Service collection
+ /// The configuration section to bind options from
+ /// Optional hook to customise the underlying , e.g. to add resilience handlers
+ /// Service collection
+ public static IServiceCollection AddCompaniesHouseClient(this IServiceCollection services,
+ IConfigurationSection section, Action? configureHttpClientBuilder = null)
+ {
+ services.AddOptions()
+ .Bind(section)
+ .ValidateDataAnnotations()
+ .ValidateOnStart();
+ return services.AddCompaniesHouseClientCore(configureHttpClientBuilder);
+ }
+
+ private static IServiceCollection AddCompaniesHouseClientCore(this IServiceCollection services,
+ Action? configureHttpClientBuilder)
+ {
services.TryAddTransient(provider =>
{
- var options = provider.GetRequiredService();
+ var options = provider.GetRequiredService>().Value;
return new StaticApiKeyProvider(options.ApiKey);
});
services.TryAddTransient();
- services.AddHttpClient((provider, client) =>
+ var httpClientBuilder = services.AddHttpClient((provider, client) =>
{
- var options = provider.GetRequiredService();
+ var options = provider.GetRequiredService>().Value;
client.BaseAddress = options.BaseUri;
})
.AddHttpMessageHandler();
+ configureHttpClientBuilder?.Invoke(httpClientBuilder);
+
+ services.TryAddCompaniesHouseSubClients();
+
+ return services;
+ }
+
+ private static IServiceCollection TryAddCompaniesHouseSubClients(this IServiceCollection services)
+ {
services.TryAddTransient(provider =>
- provider.GetService());
- services.TryAddTransient(provider =>
- provider.GetService());
+ provider.GetRequiredService());
services.TryAddTransient(provider =>
- provider.GetService());
+ provider.GetRequiredService());
services.TryAddTransient(provider =>
- provider.GetService());
+ provider.GetRequiredService());
services.TryAddTransient(provider =>
- provider.GetService());
+ provider.GetRequiredService());
+ services.TryAddTransient(provider =>
+ provider.GetRequiredService());
+ services.TryAddTransient(provider =>
+ provider.GetRequiredService());
+ services.TryAddTransient(provider =>
+ provider.GetRequiredService());
services.TryAddTransient(provider =>
- provider.GetService());
+ provider.GetRequiredService());
services.TryAddTransient(provider =>
- provider.GetService());
+ provider.GetRequiredService());
services.TryAddTransient(provider =>
- provider.GetService());
+ provider.GetRequiredService());
services.TryAddTransient(provider =>
- provider.GetService());
- services.TryAddTransient(provider =>
- provider.GetService());
+ provider.GetRequiredService());
+ services.TryAddTransient(
+ provider => provider.GetRequiredService());
services.TryAddTransient(provider =>
- provider.GetService());
+ provider.GetRequiredService());
services.TryAddTransient(provider =>
- provider.GetService());
+ provider.GetRequiredService());
+ services.TryAddTransient(provider =>
+ provider.GetRequiredService());
+
+ return services;
+ }
+
+ // ---------------------------------------------------------------
+ // Named / keyed registrations — allow several distinct, separately
+ // configured Companies House clients to coexist in the same
+ // service collection, resolved via `[FromKeyedServices(name)]` or
+ // `IServiceProvider.GetRequiredKeyedService(name)`.
+ // ---------------------------------------------------------------
+
+ ///
+ /// Registers a named companies house client, resolvable as a keyed service.
+ ///
+ /// Service collection
+ /// The name/key used to register and resolve this client
+ /// The Api Key
+ /// Service collection
+ public static IServiceCollection AddCompaniesHouseClient(this IServiceCollection services, string name,
+ string apiKey)
+ {
+ return services.AddCompaniesHouseClient(name, options => options.ApiKey = apiKey);
+ }
+
+ ///
+ /// Registers a named companies house client, resolvable as a keyed service.
+ ///
+ /// Service collection
+ /// The name/key used to register and resolve this client
+ /// The Base Uri of the API
+ /// The Api Key
+ /// Service collection
+ public static IServiceCollection AddCompaniesHouseClient(this IServiceCollection services, string name,
+ Uri baseUri, string apiKey)
+ {
+ return services.AddCompaniesHouseClient(name, options =>
+ {
+ options.BaseUri = baseUri;
+ options.ApiKey = apiKey;
+ });
+ }
+
+ ///
+ /// Registers a named companies house client, resolvable as a keyed service.
+ ///
+ /// Service collection
+ /// The name/key used to register and resolve this client
+ /// Companies house client options configuration
+ /// Optional hook to customise the underlying , e.g. to add resilience handlers
+ /// Service collection
+ public static IServiceCollection AddCompaniesHouseClient(this IServiceCollection services, string name,
+ Action configure,
+ Action? configureHttpClientBuilder = null)
+ {
+ services.AddOptions(name)
+ .Configure(configure)
+ .ValidateDataAnnotations()
+ .ValidateOnStart();
+
+ return services.AddCompaniesHouseClientCore(name, configureHttpClientBuilder);
+ }
+
+ ///
+ /// Registers a named companies house client, resolvable as a keyed service.
+ ///
+ /// Service collection
+ /// The name/key used to register and resolve this client
+ /// Companies house client options configuration
+ /// Optional hook to customise the underlying , e.g. to add resilience handlers
+ /// Service collection
+ public static IServiceCollection AddCompaniesHouseClient(this IServiceCollection services, string name,
+ Action configure,
+ Action? configureHttpClientBuilder = null)
+ {
+ services.AddOptions(name)
+ .Configure((options, provider) => configure(provider, options))
+ .ValidateDataAnnotations()
+ .ValidateOnStart();
+
+ return services.AddCompaniesHouseClientCore(name, configureHttpClientBuilder);
+ }
+
+ ///
+ /// Registers a named companies house client, binding from configuration.
+ ///
+ /// Service collection
+ /// The name/key used to register and resolve this client
+ /// The configuration to bind options from
+ /// The configuration section name (defaults to )
+ /// Optional hook to customise the underlying , e.g. to add resilience handlers
+ /// Service collection
+ public static IServiceCollection AddCompaniesHouseClient(this IServiceCollection services, string name,
+ IConfiguration configuration, string sectionName = DefaultSectionName,
+ Action? configureHttpClientBuilder = null)
+ {
+ return services.AddCompaniesHouseClient(name, configuration.GetSection(sectionName),
+ configureHttpClientBuilder);
+ }
+
+ ///
+ /// Registers a named companies house client, binding from a configuration section.
+ ///
+ /// Service collection
+ /// The name/key used to register and resolve this client
+ /// The configuration section to bind options from
+ /// Optional hook to customise the underlying , e.g. to add resilience handlers
+ /// Service collection
+ public static IServiceCollection AddCompaniesHouseClient(this IServiceCollection services, string name,
+ IConfigurationSection section, Action? configureHttpClientBuilder = null)
+ {
+ services.AddOptions(name)
+ .Bind(section)
+ .ValidateDataAnnotations()
+ .ValidateOnStart();
+
+ return services.AddCompaniesHouseClientCore(name, configureHttpClientBuilder);
+ }
+
+ private static IServiceCollection AddCompaniesHouseClientCore(this IServiceCollection services, string name,
+ Action? configureHttpClientBuilder)
+ {
+ services.TryAddKeyedTransient(name, (provider, key) =>
+ {
+ var options = provider.GetRequiredService>().Get((string)key!);
+
+ return new StaticApiKeyProvider(options.ApiKey);
+ });
+
+ var httpClientBuilder = services.AddHttpClient(name)
+ .ConfigureHttpClient((provider, client) =>
+ {
+ var options = provider.GetRequiredService>().Get(name);
+
+ client.BaseAddress = options.BaseUri;
+ })
+ .AddHttpMessageHandler(provider =>
+ new CompaniesHouseAuthorizationHandler(
+ provider.GetRequiredKeyedService(name)));
+
+ configureHttpClientBuilder?.Invoke(httpClientBuilder);
+
+ services.TryAddKeyedTransient(name, (provider, key) =>
+ new CompaniesHouseClient(provider.GetRequiredService().CreateClient((string)key!)));
+
+ services.TryAddKeyedCompaniesHouseSubClients(name);
+
+ return services;
+ }
+
+ private static IServiceCollection TryAddKeyedCompaniesHouseSubClients(this IServiceCollection services,
+ string name)
+ {
+ services.TryAddKeyedTransient(name, (provider, key) =>
+ provider.GetRequiredKeyedService(key));
+ services.TryAddKeyedTransient(name, (provider, key) =>
+ provider.GetRequiredKeyedService(key));
+ services.TryAddKeyedTransient(name, (provider, key) =>
+ provider.GetRequiredKeyedService(key));
+ services.TryAddKeyedTransient(name, (provider, key) =>
+ provider.GetRequiredKeyedService(key));
+ services.TryAddKeyedTransient(name, (provider, key) =>
+ provider.GetRequiredKeyedService(key));
+ services.TryAddKeyedTransient(name, (provider, key) =>
+ provider.GetRequiredKeyedService(key));
+ services.TryAddKeyedTransient(name, (provider, key) =>
+ provider.GetRequiredKeyedService(key));
+ services.TryAddKeyedTransient(name, (provider, key) =>
+ provider.GetRequiredKeyedService(key));
+ services.TryAddKeyedTransient(name, (provider, key) =>
+ provider.GetRequiredKeyedService(key));
+ services.TryAddKeyedTransient(name, (provider, key) =>
+ provider.GetRequiredKeyedService(key));
+ services.TryAddKeyedTransient(name, (provider, key) =>
+ provider.GetRequiredKeyedService(key));
+ services.TryAddKeyedTransient(name, (provider, key) =>
+ provider.GetRequiredKeyedService(key));
+ services.TryAddKeyedTransient(name, (provider, key) =>
+ provider.GetRequiredKeyedService(key));
+ services.TryAddKeyedTransient(name, (provider, key) =>
+ provider.GetRequiredKeyedService(key));
+ services.TryAddKeyedTransient(name, (provider, key) =>
+ provider.GetRequiredKeyedService(key));
return services;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseDocumentClientServiceCollectionExtensions.cs b/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseDocumentClientServiceCollectionExtensions.cs
index 0873fb2..c14101b 100644
--- a/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseDocumentClientServiceCollectionExtensions.cs
+++ b/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseDocumentClientServiceCollectionExtensions.cs
@@ -2,7 +2,9 @@
using CompaniesHouse;
using CompaniesHouse.DelegatingHandlers;
using CompaniesHouse.Extensions.Microsoft.DependencyInjection;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Options;
namespace Microsoft.Extensions.DependencyInjection
{
@@ -11,6 +13,12 @@ namespace Microsoft.Extensions.DependencyInjection
///
public static class CompaniesHouseDocumentClientServiceCollectionExtensions
{
+ ///
+ /// The default configuration section name used when binding
+ /// from an .
+ ///
+ public const string DefaultSectionName = "CompaniesHouseDocument";
+
///
/// Registers the companies house document client
///
@@ -19,12 +27,9 @@ public static class CompaniesHouseDocumentClientServiceCollectionExtensions
/// Service collection
public static IServiceCollection AddCompaniesHouseDocumentClient(this IServiceCollection services, string apiKey)
{
- return services.AddCompaniesHouseDocumentClient(opt =>
- {
- opt.ApiKey = apiKey;
- });
+ return services.AddCompaniesHouseDocumentClient(options => options.ApiKey = apiKey);
}
-
+
///
/// Registers the companies house document client
///
@@ -35,10 +40,10 @@ public static IServiceCollection AddCompaniesHouseDocumentClient(this IServiceCo
public static IServiceCollection AddCompaniesHouseDocumentClient(this IServiceCollection services, Uri baseUri,
string apiKey)
{
- return services.AddCompaniesHouseDocumentClient(opt =>
+ return services.AddCompaniesHouseDocumentClient(options =>
{
- opt.BaseUri = baseUri;
- opt.ApiKey = apiKey;
+ options.BaseUri = baseUri;
+ options.ApiKey = apiKey;
});
}
@@ -47,11 +52,18 @@ public static IServiceCollection AddCompaniesHouseDocumentClient(this IServiceCo
///
/// Service collection
/// Companies house client options configuration
+ /// Optional hook to customise the underlying , e.g. to add resilience handlers
/// Service collection
- private static IServiceCollection AddCompaniesHouseDocumentClient(this IServiceCollection services,
- Action configure)
+ public static IServiceCollection AddCompaniesHouseDocumentClient(this IServiceCollection services,
+ Action configure,
+ Action? configureHttpClientBuilder = null)
{
- return services.AddCompaniesHouseDocumentClient((provider, options) => configure(options));
+ services.AddOptions()
+ .Configure(configure)
+ .ValidateDataAnnotations()
+ .ValidateOnStart();
+
+ return services.AddCompaniesHouseDocumentClientCore(configureHttpClientBuilder);
}
///
@@ -59,40 +71,234 @@ private static IServiceCollection AddCompaniesHouseDocumentClient(this IServiceC
///
/// Service collection
/// Companies house client options configuration
+ /// Optional hook to customise the underlying , e.g. to add resilience handlers
/// Service collection
public static IServiceCollection AddCompaniesHouseDocumentClient(this IServiceCollection services,
- Action configure)
+ Action configure,
+ Action