# UK Public Sector Late Payers & Procurement Act Notices (`locaihost/uk-procurement-act`) Actor

Which UK public bodies pay suppliers late: every Procurement Act payments compliance notice from Find a Tender, with % paid within 30 days, average days to pay and the change since the last report. Plus contract performance ratings and terminations, supplier named. OGL data.

- **URL**: https://apify.com/locaihost/uk-procurement-act.md
- **Developed by:** [locaihost data](https://apify.com/locaihost) (community)
- **Categories:** Business, Lead generation
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.75 / 1,000 notice results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## UK late payers: Procurement Act payments compliance, contract performance and terminations (internal one-pager)

**Private Actor behind the £149 / £499 a month feed.** Not on the Store. It reads the UK Find a Tender service's two open
APIs and returns one clean row per Procurement Act 2023 notice. The headline is **which public bodies pay their suppliers
late**: every payments compliance notice, with the change since the authority's previous report. It also returns contract
performance ratings and contract terminations, with the contract and supplier named, and stable keys for every authority
and supplier.

### Late payers (payments compliance notices, UK17)

Under the Procurement Act 2023 (s.69), a contracting authority publishes a payments compliance notice for each six-month
reporting period, within 30 days of the period's end. It says how quickly the authority paid its suppliers' invoices.

| Question | Fields |
|---|---|
| How much does this authority pay within 30 days? | `paidWithin30DaysPct` (of payments made, counted from the invoice day) |
| How late is the rest? | `paid31To60DaysPct`, `paid61PlusDaysPct`, `paidAfter30DaysPct` (the two added up) |
| How long does it take on average? | `averageDaysToPay` |
| How much that was due wasn't paid at all? | `duePaymentsNotMadePct` (includes invalid or disputed invoices) |
| What does the statutory measure say? | `paidOnTermsPct` (paid within the 30-day term of s.68(2)); see the caveat below |
| Is it getting better or worse? | `paymentTrend`, `paidWithin30DaysChange` (points), `averageDaysToPayChange` (days), `paidOnTermsChange`, `previousNoticeId`, `previousPeriodStart/End`, `previousPaidWithin30DaysPct`, `previousAverageDaysToPay` |
| Which period, and which contracts? | `paymentPeriodStart`, `paymentPeriodEnd`, `contractsCovered` (Find a Tender's own words: all contracts, or Procurement Act contracts only) |
| Who is the authority? | `authorityName`, `authorityNameAsPublished`, `authorityType`, `authorityId`, `authorityIdentifiers`, `authorityKey` |
| Is this a correction? | `correctsNoticeId` (the notice it replaces) |
| Where is the official notice? | `noticeUrl`, `noticeId` |

**Coverage (read 2026-10-14).** Find a Tender has published 714 payments compliance notices, from 620 authorities,
between 8 April and 13 October 2026. 39 of them were later corrected, so there are 675 current reports. By period:
October 2025 to March 2026, 554; January to March 2026, 110; April to September 2026, 50 so far. Most notices arrive
just after a period ends: 482 came in April 2026. **The April to September 2026 reports are due by about 30 October
2026**, so expect several hundred more in the next two weeks. The Actor reads every notice Find a Tender holds on every
run (8 requests today), so nothing is missed. Scottish devolved authorities follow their own procurement rules and
are not in this data.

**Worst payers view.** The dataset's `worstPayers` view lists payments compliance notices ranked by `paidWithin30DaysPct`,
lowest first (ties: the slower average first). Reports without that figure (the short form, below) come next, then empty
reports. Apify views cannot sort, so the Actor stores rows in this order: payments compliance rows first, then the other
notice types, newest first. A run's `maxResults` cap therefore keeps the worst payers.

**Change since the previous report.** For each report, the Actor finds the same authority's most recent earlier
reporting period: one that ended before this one began. If two end on the same day, it takes the later notice. "Same
authority" means the same key or any shared identifier (PPON, Companies House, NHS, charity …). `paymentTrend` is judged on
the first figure both reports publish: % paid within 30 days, then average days (fewer is better), then the on-terms
figure. A change of less than 1 point (or 1 day) is `unchanged`. Today 41 of the 675 current reports have an earlier
period to compare with: 14 worse, 9 better, 18 unchanged. Most authorities have reported only once so far, so the
April to September reports will add a comparison for most of them.

**Things to know about the figures.**

- *Short form.* 37 current reports publish only the on-terms and unpaid figures (one adds an average of 0 days), with
  no breakdown by days. Their `paidWithin30DaysPct` is null, so they are listed after the ranked reports.
- *Empty reports.* 26 current reports publish only zeros. That usually means nothing fell due under the contracts
  covered. They are flagged `emptyReport: true` and are never ranked as worst payers or compared.
- *The on-terms figure (s.68(2)) is unreliable.* 8 notices give 0% paid on terms while their own day counts show
  invoices paid within 30 days (up to 100%). So the ranking uses `paidWithin30DaysPct`, not `paidOnTermsPct`.
- *Corrections.* Only the latest version of a report is returned. A notice that a later notice corrects is never output
  or charged, and the correction carries `correctsNoticeId`. A correction can change the figures a lot: one housing trust's
  October to March report went from 100% within 30 days to 0% across two corrections.
- *Names that are notice titles.* One publisher typed its notice title into its name ("WM Trains Payment Notice April -
  Sept 2026"). When a name ends in such a title (payment, compliance, notice or report words with a period or year), the
  Actor uses the name the same authority publishes on its other notices ("WM TRAINS LIMITED"). If it has none, the Actor
  cuts the title off. The original is always in `authorityNameAsPublished`. Ordinary names that merely contain those words
  ("Rural Payments Agency", "The Financial Reporting Council Limited") are left alone. In the full history this caught 1
  of 714 notices; in 90 days of other notices, none of 24,548 organisation names.
- *Not included.* The authority's free-text narrative and the name of the officer who approved the report. Both are on the
  official notice (`noticeUrl`).

### Contract performance (UK9) and contract terminations (UK11)

| Question | Fields |
|---|---|
| Which incumbents are scoring badly before a re-tender? | `worstRating`, `kpisBelowTarget`, `kpiSummary`, `underperformanceReasons`, `kpis[]`, `supplierNames`, `contractEnd`, `contractMaxEnd` |
| Which contracts ended, how, and what did they cost? | `terminationReason`, `terminationDate`, `terminationNotes`, `finalValue`, `finalValueGross`, `supplierNames`, `contractTitle` |
| Was a supplier thrown off a contract? | `suppliersRemoved`, `suppliersRemovedKeys` (performance notices); `terminationReason: breachOfContract` (terminations) |
| Which contract is it, how big, and when does it end? | `contractTitle`, `contractId`, `contractValue` (ex VAT), `contractValueGross`, `currency`, `contractStart`, `contractEnd`, `contractMaxEnd` (with every extension) |
| Who is the supplier, unambiguously? | `supplierNames`, `supplierCompaniesHouseNumbers`, `supplierIds`, `supplierKeys` |
| Over what period was performance judged? | `performancePeriodStart`, `performancePeriodEnd`, `kpis[].periodStart/End` |
| Is a KPI not rated yet, and why? | `kpisNotRated`, `kpis[].notes` |
| Where did the contract and supplier come from? | `joinedFromEarlierNotice` (true = from the contract details or award notice of the same procurement) |
| Where is the official notice? | `noticeUrl`, `noticeId`, `ocid` (one procurement across all its notices) |

**Ratings** (`kpis[].rating`, `worstRating`), best to worst: `met` = good, meeting or exceeding the KPI;
`slightlyBelow` = approaching target; `below` = requires improvement; then "inadequate" (on Find a Tender's scale, not
yet used by any live notice). `other` = the authority says it can't be rated yet, and `kpis[].notes` says why: it is
counted in `kpisNotRated`, never as a failure. `kpisBelowTarget` counts approaching, requires improvement and
inadequate. `underperformanceReasons` gives the authority's own explanation for each below-target KPI: its note, or
the measure when the authority wrote the explanation there (they do, for "requires improvement").

**What 90 days of live data looks like** (15 Jul – 13 Oct 2026): 27 performance notices, 96 KPI ratings: 71 met,
8 approaching target, 3 requires improvement, 14 not rated. One notice rated no KPIs and instead reported a supplier
removed from a framework. All 27 named their contract and supplier through the join described below.
By month: Jul (from the 15th) 2, Aug 6, Sep 15, Oct (to the 13th) 4. That is low but growing; more Procurement Act contracts reaching their first KPI
assessment should keep it growing (our inference, not a measured trend).

**Terminations** in the same 90 days: 180 notices covering 199 contracts. Reasons per contract: contract completed 141,
other 43, mutually agreed 14, breach of contract 3. About two a day, so bid teams can watch for incumbents leaving
early. `terminationReason` lists every reason on the notice (`;`-separated). `terminationNotes` holds the authority's
explanation (often "a duplicate notice" for `other`) and, for a breach, what went wrong. All 8 terminations in the test
set named their contract and supplier through the join. A breach notice also names the supplier itself, which is the
fallback when the join finds nothing.

**Keys for pivoting (the £499 tier).** `authorityKey` and `supplierKeys` are `GB-COH-<Companies House number>` when the
organisation has one, otherwise the identifier Find a Tender gives it (usually `GB-PPON-…`, the Central Digital
Platform id), otherwise `name:<normalised name>`. Names are normalised for case, punctuation, "&"/"and", a leading
"The" and legal suffixes ("Ltd", "Limited", "PLC", "LLP" …). Within a run, a name-only organisation takes the key of
the one identified organisation with the same normalised name, and stays `name:…` if two share it. Two more rules
make one company one key. First, every identifier published together on a party (Companies House number plus PPON) is
remembered, so a later notice that gives only the PPON resolves to the Companies House key. That memory is kept
between runs in the task's own store. So a supplier's key can change once, from its PPON to its Companies House key,
the first time a notice links the two; after that it stays put. A 7-day run sees fewer links than a 90-day one: in
the live test, one supplier kept its PPON key in a 7-day run and got its Companies House key in the 90-day run. Second, a PPON-only organisation whose exact
normalised name belongs to one Companies House (or other register) key in the run takes that key. Companies House
refuses a new company name that is the "same as" an existing one (ignoring legal suffixes, punctuation and similar
noise, much like our folding), so this is strong evidence. Payments compliance notices take part too: an authority's
key is the same in its payments, performance and termination rows. Of the 675 current payments reports, 157 authorities
are keyed by Companies House, 66 by NHS code, 51 by another register (charity, housing, education) and 401 by PPON.

*Accuracy, spot-checked 2026-10-13* on 50 rows (27 performance + 23 contract notices) and the 104 authority and
supplier keys in them, against every name and identifier published under each key across 90 days (about 14,500
notices): **103 of 104 keys correct (99%); 49 of 50 rows fully correct.** The miss was an NHS trust registered twice
on the platform under two PPONs, with no Companies House number to link them. No false merges. Shared keys behind
renames were checked too (e.g. Crown Commercial Service → Government Commercial Agency, one PPON). Across all 90 days,
distinct normalised names with more than one key fell from 2,791 to 92 with the two rules above. This was checked
against what the notices publish, not against the Companies House register itself.

### What is NOT in the feed (and why)

- **Individual payments over £30,000 (s.70)**: not in either API. No notice code or field for them appears in any
  release checked.
- **People.** No contact names, emails or phone numbers, no approving officers, and no free-text narrative from
  payments compliance notices. A supplier that looks like a private individual (a sole trader trading under their own
  name) is removed from the row. Organisations only.

### Inputs

| Input | Default | Notes |
|---|---|---|
| `noticeTypes` | payments-compliance, contract-performance, contract-termination | Also `contract-details` (UK7) and `contract-award` (UK6), opt-in. Find a Tender codes (`UK17`, `UK9`, `UK11` …) work too |
| `daysBack` | 7 | 1–365, by publication date. Above 365 is reduced to 365 |
| `authorities` | (all) | Name or identifier contains any entry as whole words ("Mace" never matches "Pharmaceuticals"); case, punctuation and Ltd/Limited ignored |
| `suppliers` | (all) | Supplier name, identifier or Companies House number contains any entry as whole words. Rows without a named supplier (every payments compliance notice) never match |
| `onlyNew` | false | Only notices this saved task hasn't returned before. The feed task turns it on |
| `maxResults` | 5000 | 0 = no limit |

Bad input ends the run **SUCCEEDED** with a message that says what to fix, and charges nothing. If Find a Tender is
down, the run also ends SUCCEEDED, with a message that names the source that failed (its notice feed, its payments
compliance API, or both). When one source fails and the other works, the run returns what the working source gave and
says which notice types are missing. If a source fails part-way, the notices read before the failure are returned and
the message says so.

### Update cadence and run time

Payments compliance notices come in two waves a year, mostly in the 30 days after 31 March and 30 September; a few
arrive at other times (corrections, late filers). Other notices are published continuously. The feed task runs weekly
with `daysBack` 14 and `onlyNew` on, so a missed week is caught up and nothing is sent twice. A correction is a new
notice, so `onlyNew` sends it even when the original went out before.

Payments compliance alone takes under a minute (8 requests). The other types come from Find a Tender's full notice
feed, which it throttles to about 20 requests per three minutes (HTTP 429 with a two-minute Retry-After; slower pacing
does not avoid it). A 7-day run reads about 25 feed pages plus one look-up per performance or termination notice whose
contract is older than the window (almost all of them): allow 5–10 minutes. A 90-day feed read took about 45 minutes
(301 pages); 365 days is about 1,200 pages, roughly 3 hours, so give long runs a long timeout. A run that reaches its
time limit stops reading, returns what it has, and says so.

### Pricing

One pay-per-event `notice` charge per row returned (plus Apify's start event). Rows dropped by filters, already
returned (`onlyNew`), superseded by a correction, or skipped are never charged. Free-plan Apify accounts are capped at
100 rows per run.

### Licence

Contains public sector information licensed under the Open Government Licence v3.0. Source: Find a Tender service
(UK Cabinet Office). The same line is in every row's `licence` field; keep it when you pass the data on.

# Actor input Schema

## `noticeTypes` (type: `array`):

Payments compliance = an authority's on-time payment record for a six-month period (UK17): % of invoices paid within 30 days, average days to pay, and the change since its previous report. Contract performance = KPI ratings for a contract (UK9); contract termination = a contract that ended, and why (UK11); both with the contract and supplier named. Contract details (UK7) and contract award (UK6) notices are opt-in. Empty = payments compliance + contract performance + contract termination.

## `daysBack` (type: `integer`):

Notices published in the last N days (1–365). Payments compliance notices come from a small separate API and are quick at any range; the other types come from Find a Tender's full notice feed, which allows only a few requests a minute: about 6 minutes per week of notices. Values above 365 are reduced to 365.

## `authorities` (type: `array`):

Keep notices whose contracting authority's name or identifier contains any of these as whole words (case, punctuation and Ltd/Limited ignored). Leave empty for all.

## `suppliers` (type: `array`):

Keep notices where any supplier's name, identifier or Companies House number contains any of these as whole words. Rows with no named supplier (every payments compliance notice) never match.

## `onlyNew` (type: `boolean`):

Return only notices this saved task hasn't returned before (memory per filter set). For scheduled feeds.

## `maxResults` (type: `integer`):

Stop after this many notices. 0 = no limit.

## Actor input object example

```json
{
  "noticeTypes": [
    "payments-compliance",
    "contract-performance",
    "contract-termination"
  ],
  "daysBack": 7,
  "onlyNew": false,
  "maxResults": 5000
}
```

# Actor output Schema

## `notices` (type: `string`):

One record per notice (dataset items, overview view).

## `summary` (type: `string`):

Pages read, notices matched per type, procurement look-ups, and any upstream problem.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {};

// Run the Actor and wait for it to finish
const run = await client.actor("locaihost/uk-procurement-act").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {}

# Run the Actor and wait for it to finish
run = client.actor("locaihost/uk-procurement-act").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{}' |
apify call locaihost/uk-procurement-act --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,locaihost/uk-procurement-act"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/m2XOuQfgiHYT80ZWR/builds/i6DpyuRSCIOvvdr1m/openapi.json
