# EASA Airworthiness Directive Delta (`titan_coder/easa-airworthiness-directive-delta`) Actor

Watches the official EASA Safety Publications portal (AD, EAD, PAD, SIB) and reports only what changed for your fleet since the last run: a newly published directive, or a revision of one you already track. Charges only per delivered event. For airlines, MRO, CAMO, lessors and insurers.

- **URL**: https://apify.com/titan\_coder/easa-airworthiness-directive-delta.md
- **Developed by:** [Radu Furtuna](https://apify.com/titan_coder) (community)
- **Categories:** Business, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$10.00 / 1,000 airworthiness directive change delivereds

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

## EASA Airworthiness Directive Delta

Durable monitor for the official **EASA Safety Publications** portal (`ad.easa.europa.eu`) — Airworthiness
Directives (AD), Emergency ADs (EAD), Proposed ADs (PAD) and Safety Information Bulletins (SIB). You give
it a **fleet watch list** — a manufacturer, a type designation, a specific AD number, an ATA chapter or a
keyword — and it reports only what genuinely changed since your last run: a **new** publication matching
your fleet, or a **revision** of one you already track. No API key, no login, no captcha.

Built for **airlines and operators, CAMOs, MRO and part-145 organisations, lessors and asset managers,
aviation insurers and airworthiness engineers** — anyone who needs to know today that an AD touching their
type has appeared or that the effective date of one they are planning around has moved.

> **This is an informational monitor of EASA publications. It is not an airworthiness management system,
> it does not confirm that a directive applies to a particular aircraft or serial number, and it does not
> replace the operator's or CAMO's own AD compliance process** — reading the full publication, assessing
> applicability and meeting the deadlines remains entirely yours. A delay, a miss or an error of this
> monitor removes no regulatory obligation.

### Cadence — read this before you buy

EASA publishes **for the whole world**, and the volume is modest. Measured live on 13.09.2026 against the
portal itself, not estimated:

| Window | ADs only | All classes (AD + EAD + PAD + SIB + ADR + SD) |
|---|---|---|
| 7 days | 12 | ~15 |
| 30 days | 30–33 | 47–50 |
| 365 days | 438 | 525–537 |

That is roughly **1–2 publications a day worldwide**, and a watch list scoped to one fleet will see
**single-digit events per month** — often zero in a quiet week. This product is deliberately built for a
watch list, not for a firehose: if you want "every EASA publication", a plain scraper is cheaper. If you
want "tell me when something touches an A320 or moves the date on AD 2026-0144", this is the right tool,
and its value is precisely that most runs cost you nothing.

Revisions are a real, recurring signal, not a theoretical one: **313 of the 2290 publications** in
2022–2026 carry a revision suffix (`2026-0144R1`), and effective dates and subjects of already-published
items do get edited.

### Source

`https://ad.easa.europa.eu/search/` — the public search of the EASA Safety Publications portal. Verified
live on 13.09.2026 from a plain HTTP client, no credentials of any kind:

- `POST /search/` (form-urlencoded, `fi_action=advanced`, `fi_date_start`, `fi_date_end`) returns HTML
  containing `Displaying records 1 to 20 out of a total of N publications`. An empty window returns an
  explicit `Sorry, no results found for your query` instead.
- `GET /search/?format=csv&r=<N>&sep=c` returns `text/csv; charset=utf-8` with a stable 7-column header:
  `AD class, Class number, Issue Date, Subject, Approval Holder / Type Designation, Effective date,
  ATA Chapter`. The export returns the **N most recent publications of all classes**, ordered by issue
  date descending; the portal caps a single export at 4000 records.
- **Stable identifier**: `Class number` (`2026-0176`, `CF-2026-45`, `US-2026-17-08`, `SAIB-2026-16`).
  Checked over the window 2022-01-01…2026-09-13: 2290 rows, 2290 unique numbers, no blanks. Each
  publication has a detail page at `https://ad.easa.europa.eu/ad/<Class number>`.
- Empty `Effective date` (231 of 2290) and empty `ATA Chapter` (218 of 2290) are normal data, not damage —
  the run only fails when a **column** disappears from the whole export.

### How it works

1. The publication window is fetched **once per run** — two HTTP requests (the count, then the export) —
   and every watch filters that same in-memory snapshot instead of hitting the EASA portal 30 times for
   identical bytes.
2. **The snapshot passes a fail-closed integrity gate before anything is classified**: content type,
   strict UTF-8 decoding, exact header match, every row exactly 7 fields, **the number of parsed rows must
   equal the number of publications the search page announced**, every issue date must fall inside the
   requested window, the order must be non-increasing, and every publication number must be non-empty and
   unique. It also enforces an absolute floor proportional to the window and a drop threshold against the
   last accepted snapshot of the same window width. Any failure turns the **whole run** into
   `source_access_limited`: no baseline, no index update, no charge. A truncated but syntactically valid
   export must never be read as "these publications no longer exist".
3. Each `watch` names one target: `approval_holder` (substring of the manufacturer/type-designation
   column), `ad_number` (a number **and all of its revisions**), `ata_chapter`, or `subject_keyword`, with
   an optional `adClasses` filter. Whether a row matches is decided **twice, independently** — once from
   the normalised fields and once recomputed from the raw CSV cells — and a disagreement fails the watch
   closed rather than delivering (and charging for) a row that may not match your target.
4. The first check of a new watch establishes a baseline (**no charge**). Every later check compares the
   current match set against the durable record of what it was last time.
5. Billing material is the **full publication row**: class, number, issue date, subject, approval holder /
   type designations, effective date, ATA chapter — hashed as canonical JSON with a full SHA-256. For an
   airworthiness directive there are no "cosmetic" columns: a moved effective date, an `- Amendment`
   appended to the subject, a changed ATA chapter and a PAD becoming an AD are all real operational
   signals.
6. A value that changes and later reverts bills **every** genuine transition (A → B → A → B), never
   silently deduplicated against an earlier occurrence of the same state.
7. **A publication leaving the window is never an event.** The window slides, so a row ageing out is
   normal, not a withdrawal — the monitor emits no "removed" signal at all and leaves the index untouched.

### Input

```json
{
  "monitorId": "my-fleet-watch",
  "lookbackDays": 30,
  "watches": [
    { "watchId": "a320-fleet",  "targetType": "approval_holder", "targetValue": "A320" },
    { "watchId": "airbus-all",  "targetType": "approval_holder", "targetValue": "AIRBUS", "adClasses": ["AD", "EAD"] },
    { "watchId": "ad-2026-0144","targetType": "ad_number",       "targetValue": "2026-0144" },
    { "watchId": "ice-protect", "targetType": "ata_chapter",     "targetValue": "30" },
    { "watchId": "fuel-pumps",  "targetType": "subject_keyword", "targetValue": "fuel pump" }
  ],
  "notifyOn": "new_alerts",
  "webhookUrl": "https://example.com/hook"
}
```

- `monitorId` — the name of this monitor's durable history. **Reuse the same value on every scheduled
  run**; a new value starts a new, empty history and re-baselines everything.
- `watchId` is permanently bound to the target it was first used with (target type, value and class
  filter). Reusing a `watchId` for a different target fails the run with `watch_config_mismatch` instead
  of quietly hiding one target's history under another's.
- `lookbackDays` (1–365, default 30) — how far back the run looks. A wider window costs nothing extra but
  must stay under the portal's 4000-record export limit, and the run fails honestly if it would not.
- Up to 30 watches per run. Substring targets must be at least 3 characters — a shorter one would match
  nearly the whole export and turn the monitor into a firehose.

### Output

Every delivered change is one dataset row: `watchId`, `watchTargetType`, `watchTargetValue`, `adNumber`,
`adFamily`, `adClass`, `changeType` (`new` | `revised`), `issueDate`, `effectiveDate`, `subject`,
`approvalHolder`, `ataChapter`, `previousIssueDate`, `previousAdClass`, `previousContentHash`,
`changeSequence`, `publicationUrl`, `contentHash`, `eventId`. (Whether a row was actually charged is
reported in `coverage.recordsBilled` and in the digest, not on the row itself: the row is written to the
dataset **before** the charge is attempted, so a row can exist for an event that ended as
`charge_unknown` — see the delivery guarantee below.)

Rows are written to **two datasets**: the run's default dataset, and a **named durable dataset**
(`easa-ad-<monitorId>`) that survives across runs and is the source of truth for everything ever
delivered. The key-value store record `coverage` reconciles every charge against every row — per-watch
status and reason, the observation window, the accepted snapshot size, new vs revised numbers, records
delivered and billed, and the claim journal size — and `digest` is a short human-readable summary written
every run.

### Pricing

Pay-per-event: you are charged **only for a delivered change** — a publication newly matching your watch
list, or a revision of one you already track. Baseline runs, runs with no changes, and runs where the
source could not be verified cost you nothing beyond platform compute.

### Delivery guarantee — honest wording

The right to write a row and to charge for it is taken from a single atomic primitive
(`RequestQueue.addRequest(uniqueKey)`) **before** any dataset write or PPE call, in a separate named claim
journal (`easa-ad-<monitorId>-claims`) that is never consumed or cleaned. The guarantee this gives is
**at-most-once, not exactly-once**: if a run dies after taking the right but before delivering, the event
is closed as `dataset_unknown` / `charge_unknown` and **is not retried** — it can be lost, but it can
never be delivered or charged twice. Losing an event was chosen deliberately over charging you twice.

Boundary of the guarantee: it holds per `monitorId`, for as long as that monitor's named storages exist.
**Boundary of migration**: renaming `monitorId`, deleting its storages, or a change of the actor's storage
prefix creates a **new, empty** claim journal — everything previously delivered becomes deliverable and
chargeable again. Treat `monitorId` as permanent.

Only one run per `monitorId` may work at a time; a second concurrent run fails fast with `lease_busy`
rather than racing it.

### Limits and honest caveats

- **Cadence is low** (see the table above). Expect single-digit events per month on a fleet-scoped watch.
- **Publication-level, not text-level.** The monitor reads the publication list, not the PDF body of a
  directive. A correction inside the document that does not touch any of the seven published columns is
  invisible to it.
- **No "withdrawn" signal.** The monitor never claims a publication was removed or superseded; ageing out
  of the window is not an event, and we have not observed EASA deleting published items.
- **Applicability is not determined.** `approval_holder` matching is a text match on the published
  "Approval Holder / Type Designation" column — it is a filter, not an airworthiness applicability
  assessment against your serial numbers, modifications and STCs.
- **A separately published revision arrives as `new`**, because EASA gives it a new publication number
  (`2026-0144R1`). An `ad_number` watch follows the whole family, so you still get it under the same watch.
- Non-EASA states' publications that EASA republishes (`CF-`, `US-`, `BR-` prefixes and similar) are
  included exactly as the portal publishes them; the primary source for those remains the issuing
  authority.
- Up to 50 changes are delivered per watch per run; the rest are picked up by the next run, so a one-off
  spike cannot drain your budget.
- If the source is unreachable or its structure changed, the run fails as `source_access_limited` **and
  charges nothing** — silence from this actor is never proof that nothing happened. Check `coverage`.

### Legal

EASA Safety Publications are official public documents of the European Union Aviation Safety Agency. This
actor reads the public portal, does not bypass any access control, and adds no data of its own. It is an
independent tool, not affiliated with or endorsed by EASA.

# Actor input Schema

## `monitorId` (type: `string`):

Name of this monitor's durable history (a-z, 0-9, dash; up to 40 chars). Reuse the same value on every scheduled run so the actor remembers which EASA publications it has already reported to you.

## `watches` (type: `array`):

1-30 watch-list entries. Each item is {"watchId": "a320-fleet", "targetType": ..., "targetValue": ..., "adClasses": \[...]}. Target types: "approval\_holder" — case-insensitive substring of the 'Approval Holder / Type Designation' column, which holds both the manufacturer (AIRBUS S.A.S., TEXTRON AVIATION INC.) and the type designations (A320, BD-500, CL-600), so one entry can watch a whole manufacturer or a single type; "ad\_number" — one publication number and all of its revisions (2026-0144 also matches 2026-0144R1); "ata\_chapter" — exact ATA chapter number (57, 92); "subject\_keyword" — case-insensitive substring of the Subject column. Optional "adClasses" narrows the entry to given classes (AD, EAD, PAD, SIB, ADR, SD). One watchId is permanently bound to the target it was first used with.

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

How far back the run looks at the EASA publication list, 1-365 days. 30 is the sensible default: EASA publishes roughly 35-50 items a month in total, so a 30-day window comfortably covers a daily or weekly schedule. A wider window costs nothing extra but must stay under the portal's 4000-record export limit.

## `notifyOn` (type: `string`):

new\_alerts — post the webhook only when paid directive events were delivered; always — post it every run; never — do not call webhookUrl at all.

## `webhookUrl` (type: `string`):

Optional. Receives a digest of delivered (paid) directive events as JSON. HTTPS only.

## Actor input object example

```json
{
  "monitorId": "my-fleet-watch",
  "watches": [
    {
      "watchId": "a320-fleet",
      "targetValue": "A320",
      "targetType": "approval_holder"
    }
  ],
  "lookbackDays": 30,
  "notifyOn": "new_alerts"
}
```

# Actor output Schema

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

Every row this run produced. Key fields: watchId, watchTargetType, watchTargetValue, adNumber, adFamily, adClass, changeType (new|revised), issueDate, effectiveDate, subject, approvalHolder, ataChapter, previousIssueDate, previousAdClass, previousContentHash, changeSequence, publicationUrl, contentHash, eventId, billed. Informational only — not an airworthiness management system and not a substitute for the operator's own AD compliance process.

## `coverage` (type: `string`):

What this run actually covered and what it charged for: per-watch status/reason/inScope/changesDetected, the observation window, accepted snapshot size, new vs revised publication ids, records delivered and billed, and the claim journal size. Enough to reconcile every charge against every row.

## `digest` (type: `string`):

A short human-readable summary of what this run found, written every run.

# 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 = {
    "monitorId": "my-fleet-watch",
    "watches": [
        {
            "watchId": "a320-fleet",
            "targetValue": "A320",
            "targetType": "approval_holder"
        }
    ],
    "lookbackDays": 30,
    "notifyOn": "new_alerts"
};

// Run the Actor and wait for it to finish
const run = await client.actor("titan_coder/easa-airworthiness-directive-delta").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 = {
    "monitorId": "my-fleet-watch",
    "watches": [{
            "watchId": "a320-fleet",
            "targetValue": "A320",
            "targetType": "approval_holder",
        }],
    "lookbackDays": 30,
    "notifyOn": "new_alerts",
}

# Run the Actor and wait for it to finish
run = client.actor("titan_coder/easa-airworthiness-directive-delta").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 '{
  "monitorId": "my-fleet-watch",
  "watches": [
    {
      "watchId": "a320-fleet",
      "targetValue": "A320",
      "targetType": "approval_holder"
    }
  ],
  "lookbackDays": 30,
  "notifyOn": "new_alerts"
}' |
apify call titan_coder/easa-airworthiness-directive-delta --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,titan_coder/easa-airworthiness-directive-delta"
        }
    }
}
```

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/QlyKadz0JzdRWeroJ/builds/zv3b8GofDyVQg2ovc/openapi.json
