# EU ETS emissions monitor (`keepable_injera/eu-ets-emissions-monitor`) Actor

Monitor verified emissions, free allocation and surrendered allowances for EU ETS installations.

- **URL**: https://apify.com/keepable_injera/eu-ets-emissions-monitor.md
- **Developed by:** [Chee Chiaw Lim](https://apify.com/keepable_injera) (community)
- **Categories:** Open source, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$2.00 / 1,000 results

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

## EU ETS emissions monitor

This Apify Actor joins the European Commission Union Registry installation metadata and yearly activity downloads. It returns one record per installation and reporting year with verified emissions, free allocation, reserve allocation, transitional allocation, and surrendered allowances.

### Input

An empty input selects all registries and the previous UTC calendar year, with a limit of 100 results. In 2026 the default reporting year is 2025. It does not search backwards if that year has no verified emissions.

| Field | Default | Meaning |
| --- | --- | --- |
| `countries` | `[]` | Registry codes such as `["DE", "PL"]`. Empty selects all. |
| `years` | Previous year | Reporting years, such as `["2025"]`. Numeric years also work through the API. |
| `activityTypeCodes` | `[]` | Optional activity codes. |
| `minVerifiedEmissions` | Unset | Minimum verified emissions in tonnes CO2 equivalent. Missing emissions fail this filter. |
| `installationIds` | `[]` | Installation identifiers. Use countries to disambiguate identifiers shared across registries. |
| `onlyNew` | `false` | Emit new and numerically changed records, skip unchanged records. |
| `maxItems` | `100` | Maximum results per run, between 1 and 10000. |
| `stateStoreName` | `eu-ets-emissions-monitor-state` | Named key-value store for snapshot history. |

### Output

Each result contains the installation and registry identifiers, installation name, reporting year, emissions, allocations, surrender total, exclusion flag, activity, permit and EPRTR identifiers, city, postal code, emission years, permitted company details, and source snapshot date. Blank and `n/a` values become `null`. Installation identifiers and codes remain strings.

The following excerpt shows the numeric fields from the supplied real sample.

```json
{
  "registryCode": "AT",
  "country": "Austria",
  "installationId": "1",
  "installationName": "Calmit Bad Ischl",
  "year": 2005,
  "verifiedEmissionsTco2e": 41399,
  "allocationFree": 44894,
  "allocationReserve": 0,
  "allocationTransitional": 0,
  "surrenderedTotal": 41399,
  "excluded": false,
  "complianceGap": 0,
  "snapshotDate": "2026-10-21",
  "generatedId": "AT:1:2005",
  "changeType": "new",
  "changedFields": []
}
```

Every complete result also contains `source`, `licence`, and `retrievedAt`. The source field names both datasets, links both downloads, and states the transformations. `complianceGap` is surrendered allowances minus verified emissions when both values exist. It is an arithmetic comparison, not a legal compliance assessment. The dataset has overview and allocation views. `RUN_SUMMARY` in the default key-value store records timestamps, fetched, eligible and pushed counts, charge-limit status, and any error. Fetched counts describe joined records that pass source filters, not every CSV row.

### Change detection

`generatedId` is `registryCode:installationId:year`. The Actor hashes verified emissions, free allocation, reserve allocation, transitional allocation, and surrendered total with SHA-256. A missing previous snapshot gives `new`. A different numeric hash gives `changed`. The same hash gives `unchanged`. `changedFields` lists the numeric fields that differ from the saved snapshot.

Metadata and snapshot-date changes do not trigger a numeric change. `onlyNew` includes both new and changed records. State advances only after a successful dataset push. Records beyond `maxItems` or the charge limit retain their previous state. Use a separate state store for each independent monitor. Avoid concurrent runs sharing one state store. There is no deletion detection.

### Pricing

Pay per result, with no run fee. The charge event is `result`, one event per saved dataset item. Unchanged records skipped with `onlyNew` do not incur a result event. This repository does not configure a Store price or deploy the Actor.

### Source credit and licence

Source: European Commission, Union Registry public data downloads. The [download page](https://union-registry-data.ec.europa.eu/api/data-download) lists these files.

- [Operators Yearly Activity Daily](https://dlsclimabi.blob.core.windows.net/public-data/eutlpublic_prod/extracts/_all_extracts/operators_yearly_activity/operators_yearly_activity_daily.csv.gz).
- [Operators Daily](https://dlsclimabi.blob.core.windows.net/public-data/eutlpublic_prod/extracts/_all_extracts/operator/operators_daily.csv.gz).

Licence text exactly as recorded in TERMS.md, from the [European Commission legal notice](https://commission.europa.eu/legal-notice_en):

"content owned by the EU on this website is licensed under the Creative Commons Attribution 4.0 International (CC BY 4.0) licence ... reuse is allowed, provided appropriate credit is given and changes are indicated. You may be required to clear additional rights if a specific content depicts identifiable private individuals".

Changes made by this Actor include joining the downloads on registry code and installation identifier, filtering records, removing personal-data fields, normalizing missing and numeric values, calculating the surrender gap, and classifying numeric changes. No endorsement by the European Commission is implied.

### Personal-data handling

The Actor drops aircraft operators when activity code is 10 or activity type is Aircraft operator. It never outputs `ACCOUNT_IDENTIFIER_IN_REG`, installation street addresses, or account holder address fields. It returns `accountHolderName` only when the metadata row has a company registration number or LEI. Otherwise that field is `null`. It returns account holder country, company registration number, and LEI where present. Tests cover these rules. Rows without matching metadata are omitted because the Actor cannot check their activity type.

### Downloads and limitations

Downloads use a one-second minimum interval per host and retry transient HTTP failures. Each gzip is streamed to a temporary CSV and checked through its final gzip trailer before parsing. Failed full downloads get up to three attempts, including truncated gzip files. Failure after the third attempt stops the run with a source URL and error. Each body download has a two-minute timeout. Temporary files are removed when collection finishes or fails.

The built-in CSV parser handles quoted commas, escaped quotes, multiline fields, UTF-8, and CRLF. It streams rows. Memory holds selected installation metadata and persisted result hashes, not the yearly activity file. The Actor needs temporary disk space for both decompressed CSVs. The memory target is below 1 GB, but this has not been measured on a live run in this environment.

The two daily files can have different snapshot dates. Metadata is current rather than historical. Results follow source order. Repeated runs with `onlyNew: false` can return the same first 100 rows. Narrow filters or enable `onlyNew` to progress through matching records. Source revisions and missing values can change results. Swiss-specific columns are not added to the EU columns. `surrenderedTotal` uses `SURR_ALL`.

### Local checks

Run `npm install`, then `npm test`. Tests use `node:test`, stub fetch, and local storage. Small fixtures in `test/fixtures` are trimmed from the CSV samples. Run `npx apify-cli run --purge` with empty input for a live check. Inspect `storage/datasets/default` and `storage/key_value_stores/default/RUN_SUMMARY.json` before relying on the results. Run `node scripts/verify-output.js` to validate the saved fields and compare the item count with the summary.

# Actor input Schema

## `countries` (type: `array`):

Registry codes such as DE and PL. Empty selects all registries.

## `years` (type: `array`):

Reporting years. Empty uses the current UTC year minus one.

## `activityTypeCodes` (type: `array`):

Optional activity type codes.

## `installationIds` (type: `array`):

Optional installation identifiers. Combine with countries to avoid collisions.

## `minVerifiedEmissions` (type: `number`):

Optional minimum verified emissions in tonnes CO2 equivalent.

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

Skip unchanged numeric snapshots.

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

Maximum results charged and saved per run.

## `stateStoreName` (type: `string`):

Persistent numeric snapshot history. Use one store per independent monitor.

## Actor input object example

```json
{
  "countries": [],
  "years": [],
  "activityTypeCodes": [],
  "installationIds": [],
  "onlyNew": false,
  "maxItems": 100,
  "stateStoreName": "eu-ets-emissions-monitor-state"
}
```

# Actor output Schema

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

No description

## `runSummary` (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("keepable_injera/eu-ets-emissions-monitor").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("keepable_injera/eu-ets-emissions-monitor").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 keepable_injera/eu-ets-emissions-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,keepable_injera/eu-ets-emissions-monitor"
        }
    }
}
```

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/bObPh14DUbW2EVEpw/builds/021FPtL8b8NSgD6tv/openapi.json
