# USCG Vessel Inspection & Deficiency Monitor (`titan_coder/uscg-vessel-inspection-deficiency-monitor`) Actor

Watches your vessels in the official USCG PSIX registry and charges only for a real change: a new port state control exam or inspection, a new deficiency, a deficiency cleared, or a case status change. For ship owners, charterers, marine insurers, P\&I clubs and maritime compliance teams.

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

## Pricing

$10.00 / 1,000 vessel inspection 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

## USCG Vessel Inspection & Deficiency Monitor

Watches the vessels you name in the official **U.S. Coast Guard PSIX** registry
([Port State Information eXchange](https://cgmix.uscg.mil/xml/PSIXData.asmx)) and reports **only what
actually changed since your last run**:

| `changeType` | What happened at the Coast Guard |
|---|---|
| `new_activity` | A **new activity appeared** on the vessel — a vessel inspection, a **port state control exam**, an incident investigation, a boarding or an operational control action. |
| `deficiency_added` | A **new deficiency was written** against an existing activity. |
| `deficiency_resolved` | A deficiency on that activity was **cleared** (`IsResolved` went to `true`). |
| `case_status_changed` | The activity's case status changed (for example `Open` → `Closed`). |
| `activity_updated` | The Coast Guard corrected another substantive field of the activity (date, type, PSC-exam flag). |

Nothing else is delivered and nothing else is charged. A run where nothing changed produces no paid
events at all.

### Who this is for

- **Ship owners and technical managers** — a standing watch on your own fleet's US port state control
  record, instead of re-checking PSIX by hand before every US call.
- **Charterers and brokers** doing pre-fixture vetting: a new PSC exam or an open deficiency on a
  candidate ship changes the decision.
- **Marine insurers, P\&I clubs and underwriters** monitoring the insured fleet's USCG inspection record
  between renewals.
- **Maritime compliance and QHSE teams** tracking when a written deficiency is finally cleared.

### How it works

1. `monitorId` names a durable history. **The first run of a new `monitorId` is a silent baseline** — it
   records the current state of every activity in each vessel's window, delivers nothing and charges
   nothing.
2. Every later run walks each vessel in three steps against PSIX — confirm the vessel, list its
   activities inside the watch window, then pull the deficiencies of **every** activity in that window —
   and compares the result against the durable per-activity index (`ActivityId -> content hash, case
   status, deficiency counts, change sequence`).
3. Each genuine change is written to the dataset, then charged **at most once** — the right to deliver
   and charge is granted by a single atomic claim keyed on `ActivityId` + change sequence + content
   hash (see *Delivery guarantee* below). A deficiency that is written, cleared,
   then written again with the **same wording** and cleared again produces **four separate paid events**,
   not two: the change sequence distinguishes repeats across the activity's history.
4. An optional HTTPS `webhookUrl` receives a digest of the delivered (paid) changes.

Run it on an Apify schedule; daily is a sensible cadence.

### Finding your `vesselId`

`vesselId` is the **internal PSIX vessel number**, not the IMO number and not the call sign. PSIX 1837 is
MAERSK IDAHO, whose IMO is 9193264. Look the number up once at
[cgmix.uscg.mil/PSIX](https://cgmix.uscg.mil/PSIX/) by vessel name, call sign or IMO, then keep it in
your input. Every delivered row carries the vessel's name, IMO/official number and flag, so a wrong
number shows up on the first event rather than a year later. A `vesselId` PSIX does not know is reported
as `watch_target_not_found` and **never** produces a silent empty baseline.

### Pricing

Pay per event: **one event per delivered inspection change**. No change, no charge. Charging happens only
*after* the row has been written to both the durable dataset and the run's own output dataset.

### Delivery guarantee: at-most-once (not exactly-once)

For any one computed change, the row is delivered to the dataset and the event is charged **no more than
once**. The right to do either is handed out by the only atomic primitive Apify offers —
`RequestQueue.addRequest(uniqueKey)` — in a separate, named request queue
(`<storage prefix>-<monitorId>-claims`) used purely as an append-only **journal of granted rights**: its
requests are never handled and never deleted. Apify's key-value store has no compare-and-swap,
conditional write or ETag, so it cannot be the source of that right; it only holds a diagnostic state
machine for reporting. The same gate also decides, atomically, which vessel and window a `watchId` is
permanently bound to.

The trade-off is deliberate: **if a run crashes after taking the claim, that change can be lost** (it is
closed as `dataset_unknown` / `charge_unknown` and never re-delivered) — but you are never charged twice
for it. Two boundaries of the guarantee, stated honestly:

- it holds for as long as the named claims queue exists. Anyone with account access can delete or
  recreate that queue through the Console/API, which starts the journal from scratch — an unavoidable
  limit of any durable storage, not a defect of the protocol;
- it applies from the build in which the claim gate shipped onward. Older builds must not keep running
  against the same `monitorId`.

### Honest limits

- **This is a registry-publication monitor, not a seaworthiness or compliance assessment.** It reports
  what the U.S. Coast Guard publishes in PSIX and when that publication changes. It is **not** a
  determination of seaworthiness, **not** evidence of SOLAS / MARPOL / ISM compliance or non-compliance,
  not a class survey and not a legal opinion. Absence of a deficiency in PSIX is not proof that a vessel
  has none. Before any fixture, purchase, insurance or port decision, use the Coast Guard's own records
  and a qualified surveyor.
- **The durable dataset is a delivery-attempt log, not a guaranteed mirror of the default dataset.**
  Each row is written to the durable dataset first, then mirrored to the run's default dataset before
  billing proceeds for that row. If the durable write succeeds but the default-dataset mirror write
  fails (e.g. transient Apify storage error), the item is marked `dataset_unknown`, billing for it is
  permanently blocked (fail-closed — we never charge for a row we can't confirm was delivered), and the
  run is not retried into re-creating that exact row. The durable dataset can therefore end up with a
  small number of orphan rows that were never mirrored and never billed. The **default dataset is the
  canonical log of rows successfully written to this run's output** (see its `run_summary` row) — but a
  default-dataset row does not by itself prove the row was billed: the row is written before
  `Actor.charge()` runs, so if charging then fails or comes back `charge_unknown`, the row is present but
  not confirmably paid. **`run_summary.eventsBilled` and Apify's own billing ledger are the source of
  truth for confirmed payment**, not the presence of a row in either dataset.
- **Update cadence and completeness are the Coast Guard's, not ours.** PSIX publishes on its own
  schedule; an exam held today may surface days later, and USCG activity only exists for vessels that
  actually call at US ports. Verified live on 13.09.2026: of 24 active vessels checked in one large
  fleet, only 3 had any PSIX activity at all since 2024. A quiet vessel produces quiet runs — and no
  charges.
- **The watch window is a rolling `lookbackDays` (default 365, max 3650) and is fixed per `watchId`.**
  Widening it later would pull in *older* activities that are not in the index yet and bill them as
  brand new, so a `watchId` is permanently bound to both its `vesselId` and its `lookbackDays`; changing
  either fails the run with `watch_config_mismatch` **before any network request**. Use a new `watchId`
  for a different window (it starts with its own free baseline).
- **A window holding more than 20 activities is refused, not silently truncated** (`target_too_broad`).
  A busy US-calling ship logged 12 activities over nearly three years, so 365 days sits far inside the
  limit — but a truncated list would make activities look like they vanished and then, on the next run,
  like they were brand new and billable, so it is refused instead.
- **An activity that drops out of the rolling window is reported, never billed, and never deleted from
  the index** (`coverage.missingFromWindowCount`). Keeping it frozen means that if the window ever shows
  it again it cannot be charged a second time as if it were new.
- **A drop in an activity's deficiency count is treated as a broken response, not as good news.** The
  Coast Guard does not delete deficiencies it has written, so "there are fewer now" means the service
  returned an incomplete answer. The vessel is reported as `source_access_limited` for that run — **no
  delivery and no billing** — and the index is left untouched so the next run recovers on its own. This
  is deliberate: silently reading it as a cleared deficiency would charge you for an improvement that
  never happened.
- **Only substantive fields drive billing.** The billing hash covers the activity's start date, type,
  case status and sub-status, PSC-exam flag and administrative-check flag, plus the text and resolved
  flag of every deficiency. Re-assigning the activity to a different USCG zone port, or churn in PSIX's
  internal identifiers, is administrative noise and is deliberately **not** charged — even though the
  row shows the newest values whenever a real change is delivered. Deficiency ordering does not matter
  either: the set is compared order-independently, so a reshuffled response is not a paid event.
- **Deficiencies have no stable ID in PSIX.** The service exposes no per-deficiency identifier, so a
  deficiency is identified by its text and resolved flag. Two deficiencies whose wording is edited by the
  Coast Guard will read as one cleared and one added.
- **Some PSIX responses are not valid XML and are repaired, not rejected.** The service embeds inspector
  free text that can contain control characters illegal in XML 1.0 (observed live in 2 of 83 activities).
  Those characters — and only those — are deterministically replaced with `U+FFFD` so one malformed
  deficiency cannot lock a vessel out of monitoring forever; the count is reported in
  `coverage.sanitizedControlCharCount`.
- **Anything else unexpected in a response is fail-closed.** A missing result element, a `soap:Fault`, a
  vessel ID or activity ID in the response that does not byte-for-byte match the one requested, a
  repeated `ActivityId`, a missing required field or a whitespace-only value all report the vessel as
  `source_access_limited` for that run — **no baseline, no delta, no billing** — rather than guessing.
- **At most 50 changes per vessel per run are delivered** (`change_cap_reached`). This protects your
  wallet from an anomalous bulk re-publication on the source side. The remaining changes are not lost —
  they are picked up by the following runs, in `ActivityId` order.
- **Only one run per `monitorId` at a time.** Concurrent runs are serialised by a durable lease; a run
  that loses the lease stops immediately, reports `lease_lost`, and never delivers or charges anything
  further.

### Output

Dataset rows carry `watchId`, `vesselId`, `vesselName`, `vesselIdentification` (IMO or official number),
`vesselFlag`, `vesselCallSign`, `vesselServiceType`, `vesselStatus`, `activityId`, `changeType`,
`activityType`, `startDtTm`, `processStatus`/`previousProcessStatus`, `processStatusDetail`,
`uscgZonePort`, `portStateControlRelatedExam`, `administrativeDeficiencyCheck`, `deficiencyCount`/
`previousDeficiencyCount`, `unresolvedDeficiencyCount`/`previousUnresolvedCount`,
`topDeficiencyDescription`, the full `deficiencies` array (description, resolved flag, system,
sub-system, system component, action, action code, failure cause, resolution text and dates), plus
`monitorId`, `runId`, `discoveredAt` and `eventId`.

`coverage` (key-value store) records, for every run: each watch's vessel, window, status, reason,
activities and deficiencies in the window and changes detected, plus run-level counters —
`recordsDelivered`, `recordsBilled`, `sourceAccessLimitedCount`, `vesselNotFoundCount`,
`targetTooBroadCount`, `changeCapReachedCount`, `missingFromWindowCount`, `sanitizedControlCharCount`.

### Source

U.S. Coast Guard PSIX SOAP service, `https://cgmix.uscg.mil/xml/PSIXData.asmx`. Public, free, no key or
registration required. Verified live on 13.09.2026.

# Actor input Schema

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

Name of this monitor's durable history (a-z, 0-9, dash; up to 40 chars). Keep it stable across scheduled runs — the first run of a new monitorId is a silent baseline and charges nothing.

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

1-4 objects: {"watchId": "maersk-idaho", "vesselId": "1837", "lookbackDays": 365}. vesselId is the internal USCG PSIX vessel number — NOT the IMO number and not the call sign (PSIX 1837 = MAERSK IDAHO, whose IMO is 9193264). Look it up once at cgmix.uscg.mil/PSIX by name, call sign or IMO. lookbackDays (30-3650, default 365) is how far back the watch window reaches; a window holding more than 20 activities is refused with a clear message asking you to shorten it. A watchId is permanently bound to its vesselId AND its lookbackDays — to change either, use a new watchId. New vessels can be added later under the same monitorId.

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

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

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

Optional. Receives a digest of delivered (paid) vessel inspection changes as JSON. HTTPS only.

## Actor input object example

```json
{
  "monitorId": "my-fleet-watch",
  "watches": [
    {
      "watchId": "maersk-idaho",
      "vesselId": "1837",
      "lookbackDays": 365
    }
  ],
  "notifyOn": "new_alerts"
}
```

# Actor output Schema

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

Every row this run produced. Key fields: watchId, vesselId, vesselName, vesselIdentification (IMO or official number), activityId, changeType (new\_activity|deficiency\_added|deficiency\_resolved|case\_status\_changed|activity\_updated), activityType, startDtTm, processStatus, previousProcessStatus, uscgZonePort, portStateControlRelatedExam, deficiencyCount, unresolvedDeficiencyCount and the full deficiencies array. Informational monitoring of a public USCG registry — not a seaworthiness or compliance assessment.

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

What this run actually covered and what it charged for: per-vessel status/reason, activities and deficiencies in each window, changes detected, records delivered and billed, requested/attempted/succeeded/failed watch counts. 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": "maersk-idaho",
            "vesselId": "1837",
            "lookbackDays": 365
        }
    ],
    "notifyOn": "new_alerts"
};

// Run the Actor and wait for it to finish
const run = await client.actor("titan_coder/uscg-vessel-inspection-deficiency-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 = {
    "monitorId": "my-fleet-watch",
    "watches": [{
            "watchId": "maersk-idaho",
            "vesselId": "1837",
            "lookbackDays": 365,
        }],
    "notifyOn": "new_alerts",
}

# Run the Actor and wait for it to finish
run = client.actor("titan_coder/uscg-vessel-inspection-deficiency-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 '{
  "monitorId": "my-fleet-watch",
  "watches": [
    {
      "watchId": "maersk-idaho",
      "vesselId": "1837",
      "lookbackDays": 365
    }
  ],
  "notifyOn": "new_alerts"
}' |
apify call titan_coder/uscg-vessel-inspection-deficiency-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,titan_coder/uscg-vessel-inspection-deficiency-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/iJjkk5bzowA3Cd4wB/builds/eS0HBG5abRuShe7Tb/openapi.json
