# EU Supplier Concentration Intelligence (`atlas-data/eu-supplier-concentration-intelligence`) Actor

Structurally paired buyer and CPV-market supplier dependency metrics from official TED XML.

- **URL**: https://apify.com/atlas-data/eu-supplier-concentration-intelligence.md
- **Developed by:** [Atlas](https://apify.com/atlas-data) (community)
- **Categories:** Business
- **Stats:** 2 total users, 1 monthly users, 30.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## EU Supplier Concentration Intelligence

Turn official EU contract award notices into buyer-level and CPV-market supplier dependency metrics.

This Apify Actor searches the anonymous official TED Search API, downloads the official XML for each accepted source notice, and follows authoritative eForms references before aggregating an award. It produces stable group and supplier IDs, fractional consortium shares, Herfindahl–Hirschman Index (HHI), top-supplier dependency, single-supplier award rate, and conservative currency-specific value metrics.

### What is safe to claim

TED Search fields such as buyer, winner, identifier, country, CPV, and value are flattened notice facets. Their array positions do not prove a lot-level relationship. This Actor therefore does not aggregate those arrays.

For supported eForms contract award XML, it resolves:

`lot result → settled contract → winning tender → tendering party → tenderer organization`

It separately proves the awarded lot and reads CPV from that lot. A winner/lot/CPV market observation is emitted only when every required reference resolves and the tender points to the same lot. There are no positional joins, inferred name-to-ID pairs, or notice-wide winner/CPV cross products.

Coverage is intentionally conservative:

- eForms `ContractAwardNotice` XML with resolvable result references is supported.
- Legacy or otherwise unsupported XML increments `excluded.unsupportedXml` and contributes no concentration record.
- A notice whose official XML cannot be obtained as a usable document is excluded and counted, never aggregated from a guess. See [Notice XML availability](#notice-xml-availability).
- Broken or incomplete award-reference paths increment `excluded.unpairable` and are omitted.
- Buyer aggregation requires exactly one root contracting organization that resolves to an XML organization. Market aggregation can still proceed when the buyer is unresolved.
- A market observation requires CPV on the structurally linked awarded lot. Notice-wide CPV is never substituted.

The structural model follows the official [TED eForms schema usage](https://docs.ted.europa.eu/eforms/latest/schema/all-in-one.html) and [competition-results mapping](https://docs.ted.europa.eu/eforms/latest/schema/competition-results.html). XML is retrieved through TED’s documented direct notice link: `https://ted.europa.eu/en/notice/{publication-number}/xml`.

### Input

The example run input below is the Actor's prefilled default. It is a bounded, reproducible
window that completes in seconds and emits a non-empty dataset:

```json
{
  "mode": "full",
  "groupBy": ["buyer", "market"],
  "buyerCountries": ["DEU"],
  "cpvPrefixes": ["48"],
  "publicationFrom": "2026-07-01",
  "publicationTo": "2026-07-07",
  "marketCpvDigits": 2,
  "maxItems": 100,
  "maxXmlDownloads": 60
}
```

Aggregates are emitted only after the query window has been scanned to exhaustion, so
`maxXmlDownloads` must be at or above the notice count of the chosen window. A run that stops on
a cap is `status: partial`, emits nothing, and resumes from the exact source row on the next run.
A run also stops on a cap it is not given in the input: the scan has a three-minute wall-clock
budget, so a source answering too slowly to finish the window leaves the same replayable cursor
rather than running until the platform kills it.

| Field | Default | Meaning |
|---|---:|---|
| `mode` | `incremental` | `full` emits snapshots; `incremental` emits changes and removals; `diff` also emits unchanged active groups. |
| `groupBy` | `["buyer","market"]` | Build buyer records, structurally proven CPV-market records, or both. |
| `buyerKeywords` | `[]` | OR match on the resolved buyer name. |
| `supplierKeywords` | `[]` | OR match on structurally resolved winning organization names. |
| `cpvPrefixes` | `[]` | OR match on CPV attached to the awarded lot. |
| `buyerCountries` | `[]` | OR match on the resolved buyer country. |
| `supplierCountries` | `[]` | OR match on resolved winning organization countries. |
| `publicationFrom`, `publicationTo` | current day | Inclusive source publication dates. Omitting dates uses a safe one-day window. |
| `marketCpvDigits` | `2` | CPV prefix width used to define a market. |
| `language` | `eng` | Preferred organization-name language, then English, then deterministic fallback. |
| `maxItems` | `1000` | Highest-award-count active groups. Removal tombstones may be added beyond this count. |
| `pageSize` | `250` | TED iteration page size. |
| `maxPages` | `100` | Maximum new source pages per run. |
| `maxScannedItems` | `10000` | Maximum new source notices per run. |
| `maxXmlDownloads` | `250` | Maximum official XML downloads per run. Must cover the whole window for aggregates to be emitted. |
| `overlapDays` | `3` | Incremental watermark overlap. |
| `stateRetentionDays` | `180` | Change-history retention. |

Filters are ANDed across categories and ORed within an array. Validated country, CPV, buyer-name, and winner-name selectors are pushed into TED expert search so selective runs do not first traverse the unfiltered source volume. The XML-resolved observation is filtered again locally before aggregation. A server-filtered query window above 4,000 notices is rejected; the safe default is one day, and explicit broad windows should be narrowed.

### Output schema 1.1.0

The authoritative dataset schema is [`.actor/dataset_schema.json`](.actor/dataset_schema.json). One active record represents a buyer or a proven CPV market for the complete observation window.

Important fields:

- `recordId`: stable logical group ID.
- `contentDigest`: compact digest used by change modes.
- `groupType`, `groupKey`, `groupLabel`: buyer or market identity.
- `observationWindow`: inclusive source publication window.
- `awardCount`, `supplierCount`, `supplierShares`: structurally paired award totals and shares.
- `concentration.hhi`: award-share HHI on the 0–10,000 scale.
- `concentration.topSupplierDependency`: largest unrounded supplier share, rounded only for output.
- `valueMetrics`: positive, usable tender values by currency; no FX conversion.
- `sourceNoticeIds`: contributing TED publication numbers.
- `associationWarnings`: explicit coverage omissions.
- `changeType`: `snapshot`, `new`, `updated`, `unchanged`, or `removed`.

#### Legal-entity identity

The XML organization reference deterministically aligns the organization’s name, country, and legal identifier.

- Legal identifiers are normalized with their scheme and registration-country context, deduplicated, sorted, and retained as a set. The same unambiguous set with a changed name remains one entity.
- Conflicting identifiers in the same scheme/country context are preserved but never selected as identity; that organization remains notice-scoped.
- Different unambiguous identifier sets remain different entities even when their names are identical.
- When no legal identifier exists, the key is scoped to the source notice and XML organization reference. Names alone are never used to merge legal entities across notices.

Identifiers are hashed in public record keys; the Actor does not expose raw identifiers as if they were globally standardized.

#### Published text

Names, identifiers, and labels are emitted as the characters TED published. The five entity
references XML predefines (`&amp;`, `&lt;`, `&gt;`, `&quot;`, `&apos;`) and numeric character
references (`&#246;`, `&#x1F600;`) are resolved once at extraction, so `Computacenter AG &amp; Co.
oHG` is emitted, matched by `supplierKeywords`, and hashed as `Computacenter AG & Co. oHG`.

Resolution is deliberately narrow, and it is not entity expansion:

- Only those five names are resolved. Any other entity name can only be declared in a DTD, and a
  notice carrying a `DOCTYPE` is refused before it is parsed, so an unknown name such as `&xxe;` is
  left as literal text rather than looked up.
- A character reference that does not denote a character XML permits — NUL, a C0 control, a lone
  surrogate, a code point past the last one — is left as written rather than decoded.
- Each reference is replaced exactly once, left to right, and the replacement is never rescanned.
  `&amp;lt;` therefore yields the text `&lt;` and can never cascade into markup.

`recordId`, `groupKey`, `contentDigest`, and every supplier ID are derived from this text, so the
run summary reports the `identityVersion` they were built under. Contract 2 is the one described
here; contract 1, written by any build before it, left the references literal.

#### Award and value allocation

Each structurally linked winning tender is one award observation. A single winner receives weight `1`; a tendering party with `n` winning organizations gives each `1/n`.

A positive tender `PayableAmount` is used only when the structurally linked winning tender has exactly one winning organization. Consortium values are omitted because TED does not state how to divide the tender value among members. Missing, invalid, negative, or zero totals also produce no value share and increment coverage counters where applicable. Currencies remain separate. Zero totals cannot produce `NaN` or infinity.

HHI is calculated from full-precision shares and rounded only after the sum of squares:

`HHI = sum(raw supplier share²) × 10,000`

Risk labels are screening aids:

- fewer than three awards: `insufficient-data`;
- HHI above 2,500 or top share at least 50%: `high`;
- HHI at least 1,500: `moderate`;
- otherwise: `low`.

### Full, incremental, diff, and removals

- `full` emits the selected active records as `snapshot` and seeds durable digest history for all computed groups, including active groups outside `maxItems`.
- `incremental` emits `new`, `updated`, and `removed` records.
- `diff` also emits `unchanged` selected active records.
- If a previously emitted group disappears or falls outside the current top N, a `removed` tombstone is emitted with zero counts and empty supplier/source arrays. Its stable `recordId` identifies the record to delete downstream.
- If an unselected group later enters the top N, it is `new` from the downstream consumer’s perspective.

When stored state was written under an older `identityVersion`, the run reports
`identityMigrated: true` and handles the two kinds of stored state differently. A partial resume
cursor is discarded and its window rescanned: half of it would describe organizations under one
text contract and half under the other, which would split or merge suppliers inside a single
record. Completed digests are kept, because a digest is a hash filed under a `recordId` rather than
a partial aggregate. A record whose identity is unchanged — every CPV market, and every buyer with
a stable legal identifier — keeps its correct `unchanged`/`updated` classification, while a group
whose key did change is retired through the ordinary removal path as a `removed` tombstone and its
successor arrives as `new`.

Only a completed source scan emits aggregates. `maxPages`, `maxScannedItems`, `maxXmlDownloads`, and `timeBudget` produce a replayable partial run. `maxItems` is applied after a complete aggregation and produces `status: partial` with `stopReason: maxItems` while preserving complete digest history.

`timeBudget` is the wall clock rather than a count. Every other cap bounds something the source
hands over, and none of them bounds how long the source takes to hand it over: a window of a few
dozen notices is a few counted downloads whether the source answers each in a fifth of a second or
in twenty. The scan therefore stops after three minutes, which leaves the one source request still
in flight — a search page or a notice download, never both — and then the records, `OUTPUT`, and
the durable snapshot inside the five minutes an Actor run is judged against. A run given a shorter platform timeout (`ACTOR_TIMEOUT_AT`) stops earlier still,
keeping back what it needs to finish writing what it already has.

### Notice XML availability

`https://ted.europa.eu/en/notice/{publication-number}/xml` is a public, throttled edge endpoint. A
bounded run must tolerate the responses it actually returns, so each response is classified before
it is aggregated:

A notice is excluded only on evidence TED itself returned about that notice. A condition that says
nothing about the notice — a security invariant, or a transport failure that never delivered an
answer — fails the run closed instead, because an unanswered request is an outage rather than an
absent notice.

| Source response | Handling |
|---|---|
| `200` with an XML document | Parsed. An XML declaration is optional; a bare root element, a namespace-prefixed root, a root name using any legal XML characters including non-ASCII ones, a generic root name, and a document led by comments or processing instructions are all accepted as XML. |
| `200` with an HTML, empty, or other non-XML body | Retried with backoff. If it never becomes XML, the notice is excluded as `excluded.malformedXml`. A body counts as an edge page when it says so: an HTML doctype, a root the document itself places in the XHTML namespace, an undecorated `html` root, or an HTML `Content-Type` on a body TED did not introduce with an XML declaration. An `html` root carrying a business namespace (`<x:html xmlns:x="urn:business"/>`) is ordinary XML and reaches the parser. |
| `404` or `410` (including TED's `<ErrorResponse>` body) | Excluded immediately as `excluded.unavailableXml`; not retried. |
| `202` | The source accepted the request and stated the notice is not ready, so the payload is never notice content and is discarded unread. Retried against a two-second deferral floor and then excluded as `excluded.throttledXml` — never as a malformed body. TED's CDN also answers an edge challenge this way, typically an empty body under an HTML content type. |
| `429` or `5xx` | Retried with backoff and `Retry-After`, from a rotated address, widening request spacing. If the budget is exhausted, the notice is excluded as `excluded.throttledXml`. TED's XML edge admits a short burst per address and then answers `429` until it drains, which it does in well under a second, so request spacing has a floor below that burst and recovers as fast as it widens. |
| Other `4xx` | Excluded as `excluded.unavailableXml`; not retried. |
| Body above the 5 MB cap | Streaming stops and the notice is excluded as `excluded.oversizeXml`. |
| Valid XML that is not supported eForms, such as legacy `TED_EXPORT` | Excluded as `excluded.unsupportedXml`. |
| A DNS, TLS, connection, timeout, abort, or truncated-body failure | Retried to the bounded budget, then the run fails closed. No answer was received, so the notice is never written off as unreadable. |
| A `3xx` redirect, or a non-`ted.europa.eu` HTTPS XML URL | The run fails closed. These are integrity invariants, not source variance. |

Exclusions are per notice: one unreadable notice can never collapse a run. They stop being treated
as coverage once they become systemic — if at least three XML downloads have been attempted in a
run and more than half of them failed, the run fails closed rather than publishing an aggregate
that silently under-counts its window. That ratio needs a sample, so a run in which every attempted
download failed also fails closed regardless of how few notices it attempted: a one- or two-notice
window with nothing readable in it is an outage, never a successful empty result.

Every run summary reports `xmlFailures`, the per-reason counters above, and `xmlFailureSamples`, a
bounded sample of the excluded notices with the source-side reason for each.

### Reliability and bounded state

- TED Search and XML downloads use bounded retries, exponential backoff, `Retry-After` in both the delta-seconds and HTTP-date forms RFC 9110 allows, a 30-second request timeout, adaptive request spacing that widens under throttling, trusted HTTPS XML URLs, and a 5 MB XML cap.
- Retries are bounded by time as well as by count, on both source paths. One notice may spend twenty seconds across all of its attempts, and a search ladder may spend only what the run has left; no attempt is started, and no backoff waited out, past the deadline the run is working to — a request timeout is shortened to whatever remains. A retry budget counts answers, and a source can answer every one of them slowly; the clock is the bound that a count cannot express. A search that runs out of the run's time stops the scan on the page it could not fetch and leaves that page as the resume cursor, rather than failing a run whose earlier pages are already durable.
- Obtaining and retiring an egress address is charged to the same budget the download is. Composing a proxy URL is a network call and retiring a tunnel tears down a live socket, neither with an abort of its own, so both are abandoned on the deadline rather than allowed to hold a bounded run open. An abandoned operation is left with its rejection handled, so a late failure quoting the proxy URL can neither crash the run nor print the account's password.
- A notice whose budget runs out having never received an answer fails the run closed, exactly as an exhausted retry count does. Running out of time is never converted into evidence about a notice that was never read.
- Notice XML downloads egress through Apify's shared datacenter proxy, obtained from the Apify SDK. The tunnel is held while it is serving notice content and rotated the moment it is not, so a deferral, a rate limit, or an edge page is retried from a different address instead of from the one that produced it, while a run that finds a working address keeps it for the notices that follow. No session identifier is ever sent, since a sticky session would pin a run to the address being challenged. TED's edge challenges an egress address for a whole run at a time, which rotation is what escapes.
- The proxy is applied narrowly and never widens what the Actor trusts. Only the allowlisted `ted.europa.eu` notice XML endpoint is proxied — TED Search always uses direct egress — and the URL is proven to be TED's own before any tunnel is opened. Only a URL Apify Proxy itself composed is dialled — its public endpoint, or the host the platform configured for the run — so an arbitrary proxy URL cannot be supplied through the input, a residential identity is refused, and URL validation, redirect refusal, and the bounded retry budget are unchanged. The proxy URL carries the account's proxy password in its userinfo and is never logged, returned, or placed in an error message. Where Apify Proxy is unavailable, such as a local run, the Actor logs the loss of rotation and continues over direct egress.
- Pagination uses TED iteration tokens. Expired tokens replay from the start with a bounded duplicate ledger; conflicting source identities fail closed.
- Fully handled pages are checkpointed before later source work. Aggregates are emitted only after source exhaustion.
- The durable state lease is renewed on a cadence throughout the run, including inside a single page of notice downloads, so a scan that takes longer than the lock still holds it when it writes. The lease is separately re-proved immediately before `OUTPUT` and before the durable snapshot, which is the boundary that fences a concurrent run.
- A slow scan reports its progress — counters and elapsed time only, never a URL or anything the source returned — so a run that is taking too long can be diagnosed from its log rather than from its duration.
- Durable state is capped at 3.5 MB, six configurations, 4,000 source notices per window, 2,000 groups, and 500 suppliers per group.
- Resumes expire after seven days or twenty attempts so abandoned cursors cannot occupy state forever.
- A named request-queue lease prevents overlapping writers, and each state write is an immutable snapshot whose generation is the lock expiry the platform itself issued — that expiry and nothing else. A successor is granted the lock only after the incumbent's has expired, and every grant is stamped from one server clock, so a successor's generation always exceeds every generation the incumbent held. A writer suspended past its own expiry can therefore still land a snapshot, but never one that outranks the successor's, whatever order the two writes arrive in. Local expiry checks fence a stale owner before writes, and a prolongation that reported an earlier expiry than one already held would fail the run closed rather than be written through.
- Dataset output is written before `OUTPUT`, and the final durable state snapshot is written last. A failed final state write may replay stable-ID rows but cannot silently advance state past missing output.

### Run locally

```bash
npm ci
npm run lint
npm run typecheck
npm run build
npm test
npm run smoke:local
LIVE_SMOKE=1 npm run smoke:live
npm audit --omit=dev
npm audit
npm run validate:schema
```

The live smoke executes the real `runActor` path against TED Search and official notice XML: once
with a three-notice cap, and once with the documented example run input, which must emit a
non-empty dataset inside five minutes.

### Data source and reuse

- Source: [TED](https://ted.europa.eu/)
- Search API: [official TED Search API documentation](https://docs.ted.europa.eu/api/latest/search.html)
- Field reference: [TED Search field list](https://docs.ted.europa.eu/ODS/latest/reuse/field-list.html)
- XML direct links: [TED notice-view help](https://ted.europa.eu/en/help/notice-view)
- Reuse terms: [TED legal notice](https://ted.europa.eu/en/legal-notice)

TED is the source of the notices. This Actor’s metrics and risk labels are derived analytics, not official EU assessments or legal advice.

# Actor input Schema

## `mode` (type: `string`):

full emits snapshots; incremental emits new/updated/removed groups; diff also emits unchanged groups.

## `groupBy` (type: `array`):

Buyer requires one XML-resolved contracting organization; market uses CPV codes on the awarded XML lot.

## `buyerKeywords` (type: `array`):

Case-insensitive OR match against official buyer names.

## `supplierKeywords` (type: `array`):

Case-insensitive OR match against official winner names.

## `cpvPrefixes` (type: `array`):

Server-prefilter notices, then keep awards whose structurally linked lot has a matching CPV prefix.

## `buyerCountries` (type: `array`):

Official three-letter TED country codes; OR within this filter.

## `supplierCountries` (type: `array`):

Server-prefilter notices, then match structurally resolved winning organizations.

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

Inclusive YYYY-MM-DD. Omit for the safe single-day default window.

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

Inclusive YYYY-MM-DD.

## `marketCpvDigits` (type: `integer`):

Digits used to define each market; 2 is broad, 8 is specific.

## `language` (type: `string`):

Falls back to English and then the first available localized value.

## `maxItems` (type: `integer`):

Highest-award-count active groups. Incremental/diff tombstones may be emitted in addition.

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

Advanced; official maximum 250.

## `maxPages` (type: `integer`):

Duplicate-only token replay has a separate hard ceiling.

## `maxScannedItems` (type: `integer`):

Sparse filters still advance safely. The server-filtered window is separately capped at 4,000 notices.

## `maxXmlDownloads` (type: `integer`):

Bounds authoritative notice downloads; partial runs resume at the exact source row. Aggregates are emitted only once the window is fully scanned, so keep this at or above the notice count of the chosen window.

## `overlapDays` (type: `integer`):

Revisit recent publications to detect source edits.

## `stateRetentionDays` (type: `integer`):

Completed group digests older than this can be forgotten.

## Actor input object example

```json
{
  "mode": "full",
  "groupBy": [
    "buyer",
    "market"
  ],
  "buyerKeywords": [],
  "supplierKeywords": [],
  "cpvPrefixes": [
    "48"
  ],
  "buyerCountries": [
    "DEU"
  ],
  "supplierCountries": [],
  "publicationFrom": "2026-07-01",
  "publicationTo": "2026-07-07",
  "marketCpvDigits": 2,
  "language": "eng",
  "maxItems": 100,
  "pageSize": 250,
  "maxPages": 100,
  "maxScannedItems": 10000,
  "maxXmlDownloads": 60,
  "overlapDays": 3,
  "stateRetentionDays": 180
}
```

# Actor output Schema

## `dataset` (type: `string`):

Versioned XML-paired buyer and CPV-market HHI, dependency, supplier share, value metrics, and removal tombstones.

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

Traversal, exclusions, changes, cap status, query window, and source attribution.

# 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 = {
    "mode": "full",
    "cpvPrefixes": [
        "48"
    ],
    "buyerCountries": [
        "DEU"
    ],
    "publicationFrom": "2026-07-01",
    "publicationTo": "2026-07-07",
    "maxItems": 100,
    "maxXmlDownloads": 60
};

// Run the Actor and wait for it to finish
const run = await client.actor("atlas-data/eu-supplier-concentration-intelligence").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 = {
    "mode": "full",
    "cpvPrefixes": ["48"],
    "buyerCountries": ["DEU"],
    "publicationFrom": "2026-07-01",
    "publicationTo": "2026-07-07",
    "maxItems": 100,
    "maxXmlDownloads": 60,
}

# Run the Actor and wait for it to finish
run = client.actor("atlas-data/eu-supplier-concentration-intelligence").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 '{
  "mode": "full",
  "cpvPrefixes": [
    "48"
  ],
  "buyerCountries": [
    "DEU"
  ],
  "publicationFrom": "2026-07-01",
  "publicationTo": "2026-07-07",
  "maxItems": 100,
  "maxXmlDownloads": 60
}' |
apify call atlas-data/eu-supplier-concentration-intelligence --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,atlas-data/eu-supplier-concentration-intelligence"
        }
    }
}
```

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/3oJY6OeOGn0Ukr392/builds/2extHa49jGmwmWTTh/openapi.json
