# UK Public Cleaning & FM Contract Renewal Leads (`afkdigital/newapi16-uk-cleaning-fm-contract-renewals`) Actor

Official UK cleaning and FM contract expiry-watch leads from Find a Tender and Contracts Finder OCDS. Buyer, supplier, CPV, dates, GBP values and notice URLs. Expiry is not a confirmed re-tender or supplier switch.

- **URL**: https://apify.com/afkdigital/newapi16-uk-cleaning-fm-contract-renewals.md
- **Developed by:** [AFK Digital](https://apify.com/afkdigital) (community)
- **Categories:** Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$6.00 / 1,000 unique uk cleaning/fm contract records

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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 Public Cleaning & FM Contract Renewal Leads

**NEWAPI16** · v1.0.0 · Actor: newapi16-uk-cleaning-fm-contract-renewals

Find published end dates of public-sector cleaning and facilities-management (FM) contracts in the United Kingdom. Official **Find a Tender** and **Contracts Finder** Open Contracting Data Standard (OCDS) notices are normalized into one row per identifiable contract/award. Results are available in JSON and CSV for Clay, n8n and CRM enrichment.

**Important:** A contract approaching its stated end date is **not** a confirmed renewal tender, supplier change, or guaranteed sale. Some source dates are planning estimates. Supplier names are included only when directly linked to awards. Neither contractual expiry nor actual supplier switch is guaranteed by this data.

### Quick start

Default scan: cleaning CPV group 90910000 and FM group 79993000; the next 365 days of contractual end dates; the last 60 days of source publication/update activity; up to four pages per source; up to ten unique records; and a $0.06 event spending cap. Small bounded defaults are intended to complete within five minutes.

Example input (Apify Console or API):

```
{
  "cpvCodes": ["90910000", "79993000"],
  "region": "",
  "contractEndFrom": "2026-10-22",
  "contractEndTo": "2027-10-22",
  "maxResults": 10,
  "maxCostUsd": 0.06,
  "lookbackDays": 60,
  "maxPagesPerSource": 4,
  "pageSize": 100,
  "resume": true
}
```

- Trailing zeroes in a CPV input are treated as a CPV-family prefix, not exact-equality. For example, 90910000 covers 9091xxxx. Source data must explicitly classify the procurement with CPV; no category is inferred from its title.
- Dates are inclusive YYYY-MM-DD. When omitted, the range runs from today UTC to today + 365 days.
- Optional region text matches an official delivery location or the buyer's address (fall-back marked buyer_address_fallback). It is not a certified service-area match.
- Optional publicationFrom/publicationTo ISO date-times override lookbackDays.
- This is a bounded **published/update history search**, not an exhaustive UK contract inventory.

### Official APIs and licence

1. Find a Tender OCDS 1.1.5 release packages: https://www.find-tender.service.gov.uk/api/1.0/ocdsReleasePackages. Stage award, limit at most 100, cursor from links.next. Official documentation: https://www.find-tender.service.gov.uk/apidocumentation/1.0/GET-ocdsReleasePackages. Filter dates are updatedFrom and updatedTo.
2. Contracts Finder OCDS release packages: https://www.contractsfinder.service.gov.uk/Published/Notices/OCDS/Search. Stages award,implementation; limit at most 100; cursor from links.next. Official documentation: https://www.contractsfinder.service.gov.uk/apidocumentation/Notices/1/GET-Published-Notice-OCDS-Search. Filter dates are publishedFrom and publishedTo.
3. Both observed release packages declare the **Open Government Licence v3.0**: http://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/. Attribution: Contains public sector information licensed under the Open Government Licence v3.0. Official source URLs and notice IDs are preserved.

The two publication date predicates have different meanings. Older contracts not updated recently will not be found in the default short lookback. Expand lookback, and page caps, for broader historical searches. Do not assume complete nationwide coverage.

### Contract relationship and evidence

| Output field | Official source mapping |
| --- | --- |
| notice_id, ocid | OCDS release.id and release.ocid |
| tender_title | release.tender.title |
| buyer_authority | release.buyer.name or referenced buyer party |
| supplier_name | linked awards\[].suppliers\[], resolved via parties\[], never inferred for planned tenders |
| contract_start_date, contract_end_date | contracts\[].period, if explicitly given; otherwise related awards\[].contractPeriod; tender.contractPeriod only for a record without awards or contracts, explicitly flagged planned |
| contract_value_gbp | contract.value, award.value, or tender.value, **only when currency is GBP**, with source-specific status |
| cpv_codes | explicit OCDS tender/item/lot/award/contract CPV classifications |
| source_notice_url | Official original notice link |
| observed_at | Date-time at collection |

Evidence columns:

- date_status: signed_contract_scheduled_end, recorded_contract_period, awarded_period_not_verified, tender_planned. A signed contract may still be extended or changed; dates are scheduled, not legally guaranteed.
- value_status: recorded_contract_value, awarded_value, estimated_tender_value, unavailable_or_non_gbp.
- supplier_party_ids, award_id, contract_id, ocid, release_date and contract_key identify related data.
- region_match_basis: service_location or buyer_address_fallback.
- renewal_signal is always expiry_watch_only_not_a_confirmed_renewal.
- No contract end date is ever calculated from a duration, tender date, award date or notice date. A missing contractual end date does not become a result.

### Pricing

The intended paid Apify pay-per-event (PPE) event is **unique-contract-record at $0.006** per unique output contract. Therefore 1,000 records cost **$6.00** in event fees. There is no paid startup event. The Apify account must additionally be configured in **Publishing > Monetization** and actual Store pricing verified; having event-charge code alone does not activate pricing.

Limits maxResults, maxCostUsd, and the Apify platform maximum charge apply together. In paid mode, one Actor.charge event with an idempotency key is associated with one unique dataset result. There is no billing on raw notice views, duplicate release updates, or unmatched results. Prior to monetization, development/test builds are not paid.

The Actor deduplicates source:OCID:contract or award identity, and conservatively suppresses cross-publisher matches only when buyer, supplier, contract title, start/end dates and value all match. On resumed runs, checkpoint and existing dataset results prevent already emitted contracts being processed twice. Apify charge idempotency keys have a finite time window; an interrupted charge before a successful dataset write may require an operator check. New separate runs have independent billing contexts. To avoid duplicate CRM leads **across separate runs**, upsert by contract_key/ocid and correlate cross-source identifiers explicitly.

Apify can retain 20% of the Actor's revenue and deduct platform usage from the creator's share (if usage is included); confirm plan-specific and run-specific costs on the real Apify billing page. Downloading stored data after a run may carry storage usage charges.

### Resilience and support

- HTTP timeout: 15 seconds; temporary 429 or 5xx errors: at most three controlled retries with backoff.
- Enforced HTTPS source-host/path validation for cursor links.
- Bounded pages and results; source failure recorded in RUN_SUMMARY, preserving partial results; if both fail the run reports failure.
- Within-run CHECKPOINT stores emitted contract keys, cross-source dedupe fingerprints, source cursor, source index and input hash. Resume is within the same run's storage, not a shared account-level global database.
- No API keys are required for the public government OCDS endpoints. Credentials are not embedded or logged.

### Clay, n8n and CSV / JSON

The Apify default dataset provides row-oriented JSON via the Dataset Items API, and native CSV with the same endpoint using format=csv. The Actor Output schema advertises results, csv, and RUN_SUMMARY locations. In Clay, import CSV or feed the JSON endpoint and use contract_key as the upsert key. In n8n, run the Actor, fetch its dataset, and branch on date_status/value_status before creating a follow-up. Avoid wording that promises a renewal tender or incumbent replacement.

### Quality assurance

```
cd /opt/NEWAPI/NEWAPI16
python3 -m unittest discover -s tests -v
docker build -t newapi16:1.0.0 .
NEWAPI16_LOCAL_TEST=1 python -m src.main
```

Release-source checks and benchmark evidence should be stored under evidence/ without secrets. This is a source-monitoring product, not procurement/legal advice.

# Actor input Schema

## `cpvCodes` (type: `array`):

CPV groups or full 8-digit codes. Trailing zeroes are treated as classification family prefixes (90910000 includes 9091xxxx).

## `region` (type: `string`):

Matches official service-delivery location where present; falls back to buyer address with region_match_basis flag. Empty means all UK.

## `contractEndFrom` (type: `string`):

YYYY-MM-DD inclusive; blank uses today's date UTC.

## `contractEndTo` (type: `string`):

YYYY-MM-DD inclusive; blank uses today + 365 days UTC.

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

No more than this many unique contracts are emitted / billed.

## `maxCostUsd` (type: `number`):

Hard Actor-side cap in addition to the Apify run max charge. E.g., $0.06 for 10 unique records, $6 for 1,000.

## `lookbackDays` (type: `integer`):

Find a Tender uses updatedFrom; Contracts Finder uses publishedFrom. Search window is not a complete historical contract census.

## `maxPagesPerSource` (type: `integer`):

Each source provides up to 100 releases per page. Default keeps automated QA below five minutes.

## `pageSize` (type: `integer`):

Maximum official OCDS releases per page, 1 to 100.

## `publicationFrom` (type: `string`):

ISO-8601 date-time, e.g. 2026-09-01T00:00:00. FTS updatedFrom, CF publishedFrom.

## `publicationTo` (type: `string`):

ISO-8601 date-time; FTS updatedTo, CF publishedTo.

## `resume` (type: `boolean`):

Continue from checkpoint within the same run and skip previously emitted contract keys.

## Actor input object example

```json
{
  "cpvCodes": [
    "90910000",
    "79993000"
  ],
  "region": "",
  "contractEndFrom": "",
  "contractEndTo": "",
  "maxResults": 10,
  "maxCostUsd": 0.06,
  "lookbackDays": 60,
  "maxPagesPerSource": 4,
  "pageSize": 100,
  "resume": true
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `csv` (type: `string`):

No description

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

No description

# 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("afkdigital/newapi16-uk-cleaning-fm-contract-renewals").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("afkdigital/newapi16-uk-cleaning-fm-contract-renewals").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 afkdigital/newapi16-uk-cleaning-fm-contract-renewals --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,afkdigital/newapi16-uk-cleaning-fm-contract-renewals"
        }
    }
}
```

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/ChPzxZbGPbI285Twv/builds/tAWmarUtAYZTI1w2y/openapi.json
