# FMC Oti Scraper (`normdata/fmc-oti-scraper`) Actor

The full FMC registry of licensed Ocean Transportation Intermediaries: every US and foreign NVOCC and ocean freight forwarder, with license number, renewal date, surety bond and amount, qualifying individuals and address. Filter for compliance and partner vetting, or look up an OTI by number.

- **URL**: https://apify.com/normdata/fmc-oti-scraper.md
- **Developed by:** [Norm Data](https://apify.com/normdata) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.20 / 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.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## 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

![Norm Data](https://i.ibb.co/rGbhM5Y8/Chat-GPT-Image-Sep-8-2026-02-20-50-PM.png)

## FMC OTI Scraper

Get the full **FMC registry of licensed Ocean Transportation Intermediaries** with no API key
and no login. Every **NVOCC** and **ocean freight forwarder**, US and foreign, that is licensed
or bonded with the **Federal Maritime Commission**: about 9,800 entries with license number,
license renewal date, **surety bond company and amount**, qualifying individuals, trade names,
country and full address. Built for **OTI compliance and partner vetting**, freight-forwarder
and NVOCC due diligence, KYB, B2B lead generation and surety-market research.

Here is one real row:

```json
{
  "organization_number": "027971",
  "name": "CROWLEY GOVERNMENT SERVICES, INC.",
  "dba_names": [],
  "oti_types": ["NVOCC", "Ocean Freight Forwarder"],
  "is_nvocc": true,
  "is_freight_forwarder": true,
  "license_number": "027971",
  "is_licensed": true,
  "renewal_date": "2028-01-31",
  "days_to_renewal": 507,
  "renewal_status": "current",
  "country": "UNITED STATES",
  "is_us_based": true,
  "is_foreign_nvocc": false,
  "address": "9487 REGENCY SQUARE BLVD., JACKSONVILLE, FL, 32225, UNITED STATES",
  "state": "FL",
  "phone": "(904) 727-2200",
  "nvocc_bond": {
    "surety": "LIBERTY MUTUAL INSURANCE COMPANY",
    "bond_number": "190044893",
    "amount": 75000,
    "effective_date": "2020-05-07"
  },
  "freight_forwarder_bond": {
    "surety": "LIBERTY MUTUAL INSURANCE COMPANY",
    "bond_number": "190042466",
    "amount": 50000,
    "effective_date": "2019-04-01"
  },
  "bond_amount_total": 125000,
  "has_bond_on_file": true,
  "primary_surety": "LIBERTY MUTUAL INSURANCE COMPANY",
  "qualifying_individual": "RENE CORMIER",
  "qualifying_individuals": [{ "name": "RENE CORMIER", "title": "OTI COMPLIANCE OFFICER" }],
  "fmc_oti_list_url": "https://www.fmc.gov/licensing-and-certification/",
  "scraped_at": "2026-09-10T19:23:39.213Z"
}
```

### What it does

Reads the full FMC list of licensed and bonded Ocean Transportation Intermediaries in one pass,
then normalizes every entry into a flat row. Two modes:

- **Search** filters the registry into a list: by name, OTI type, US state, country, US against
  foreign, city, surety company, qualifying individual, bond amount, or license renewal date.
- **Look up** resolves exact FMC organization numbers, license numbers, or company names, one
  row per identifier. An identifier that does not resolve comes back as an `error` row.

Missing source values are returned as `null`, never invented.

### Why this scraper

- **Surety bond company and amount, on every row.** The NVOCC bond and the forwarder bond, each
  with its surety company, bond number, amount and effective date. This is the field a party
  vetting an intermediary actually needs, and no other Apify actor exposes it.
- **License renewal tracking.** `renewal_date`, `days_to_renewal` and a `renewal_status` of
  `current`, `due_soon` or `overdue`, so you can build an expiring-license watch list in one
  run.
- **The foreign NVOCC angle.** `is_foreign_nvocc` and `unlicensedOnly` isolate non-US NVOCCs,
  the group the FMC advises US shippers and forwarders to check most carefully, and flags the
  bonded-only ones that hold no FMC license.
- **Qualifying individuals.** The named people the FMC holds responsible for each licensed OTI,
  with their titles, not just a company name.
- **Complete in one request.** The whole registry, about 9,800 entries, comes back in one
  keyless request. A full run finishes in well under a minute.
- **No API key, no login, no browser.** Reads only what the FMC publishes.

### How it compares

| Capability | This actor | Other FMC / OTI data on Apify |
|---|:--:|:--:|
| Full licensed OTI registry, one row per intermediary | yes | rare |
| NVOCC and ocean freight forwarder in one dataset | yes | no |
| **Surety bond company + amount + effective date** | **yes** | **no** |
| **License renewal date + overdue flag** | **yes** | **no** |
| Foreign vs US, foreign-NVOCC and unlicensed filters | yes | no |
| Qualifying individuals with titles | yes | no |
| Trade names (DBA) | yes | no |
| Look up by organization or license number | yes | no |
| Declared dataset schema + free-plan preview | yes | rare |

### Use cases

- **OTI compliance and partner vetting.** Before you tender cargo to an NVOCC or forwarder,
  confirm it is licensed or bonded, read its bond amount and surety, and see who its qualifying
  individual is.
- **KYB and onboarding.** Screen a counterparty against the federal registry and keep the
  license number, bond and address on file.
- **Expiring-license monitoring.** Pull every OTI whose renewal is due within your window, or
  already overdue, for a renewal-outreach or risk list.
- **B2B lead generation.** Build a targeted list of NVOCCs or forwarders by state, country or
  size of bond, with phone and officer names.
- **Surety and insurance market research.** Rank surety companies by how many OTI bonds they
  write and for what amounts.
- **Freight-tech and TMS enrichment.** Attach FMC license status and bond data to carrier and
  vendor records.

### Quickstart

Search: NVOCCs in Florida whose license renewal is due within the next 180 days.

```json
{
  "mode": "search",
  "state": ["FL"],
  "otiType": "nvocc",
  "renewalDueInDays": 180,
  "maxItems": 100
}
```

Foreign NVOCCs with a bond on file:

```json
{ "mode": "search", "foreignNvoccOnly": true, "hasBond": true, "maxItems": 500 }
```

Every OTI bonded by one surety company:

```json
{ "mode": "search", "suretyCompany": "Avalon Risk Management", "maxItems": 1000 }
```

Look up a batch:

```json
{ "mode": "lookup", "identifiers": ["027971", "019738", "CROWLEY"] }
```

### Input reference

| Field | Applies to | Description |
|---|---|---|
| `maxItems` | all | Ceiling on rows written. |
| `mode` | all | `search` or `lookup`. |
| `name` | search | Legal or trade name contains this text. |
| `otiType` | search | `any`, `nvocc`, `freight_forwarder`, or `both`. |
| `state` | search | Two-letter US state code(s). |
| `country` | search | Full country name(s), e.g. `CHINA`, `UNITED STATES`. |
| `basedIn` | search | `any`, `us`, or `foreign`. |
| `city` | search | City contains this text. |
| `foreignNvoccOnly` | search | Only non-US NVOCCs. |
| `unlicensedOnly` | search | Only non-US NVOCCs that hold no FMC license (bonded only). |
| `suretyCompany` | search | NVOCC or forwarder bond surety company contains this text. |
| `qualifyingIndividual` | search | A named qualifying individual contains this text. |
| `hasBond` | search | `true` for OTIs with a bond amount on file, `false` for those without. |
| `minBondAmount` / `maxBondAmount` | search | Combined bond amount range in USD. |
| `renewalBefore` / `renewalAfter` | search | License renewal-date window (ISO date). |
| `renewalDueInDays` | search | Renewal due within this many days, including any already overdue. |
| `identifiers` | lookup | Organization numbers, license numbers, or company names. |

### Output reference (selected)

| Field | Description |
|---|---|
| `organization_number` | The FMC organization number. |
| `name` / `dba_names` | Legal name and registered trade names. |
| `oti_types` / `is_nvocc` / `is_freight_forwarder` | Intermediary type. |
| `license_number` / `is_licensed` | License number, and whether a license is held (false for bonded-only foreign NVOCCs). |
| `renewal_date` / `days_to_renewal` / `renewal_status` | License renewal timing (`current`, `due_soon`, `overdue`). |
| `country` / `is_us_based` / `is_foreign_nvocc` | Location and the foreign-NVOCC flag. |
| `street_1` / `street_2` / `city` / `state` / `zip` / `address` / `phone` / `fax` | Contact. |
| `nvocc_bond` / `freight_forwarder_bond` | Bond object: `surety`, `bond_number`, `amount`, `effective_date`. |
| `bond_amount_total` / `has_bond_on_file` / `primary_surety` | Bond rollup. |
| `qualifying_individual` / `qualifying_individuals` | The named responsible people and titles. |
| `query` / `error` | Set on an unresolved look-up identifier row. |
| `fmc_oti_list_url` | The FMC's public OTI list page. |
| `scraped_at` | ISO 8601 collection timestamp. |

### Run via API and CLI

```bash
curl -X POST "https://api.apify.com/v2/acts/USERNAME~fmc-oti-scraper/run-sync-get-dataset-items?token=<TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"mode":"lookup","identifiers":["027971"]}'
```

```bash
apify call USERNAME/fmc-oti-scraper --input '{"mode":"search","foreignNvoccOnly":true,"maxItems":1000}'
```

### Fetch results

```bash
curl "https://api.apify.com/v2/datasets/<DATASET_ID>/items?token=<TOKEN>&clean=true&format=json"
curl "https://api.apify.com/v2/datasets/<DATASET_ID>/items?token=<TOKEN>&clean=true&format=csv"
```

### Billing and limits

- **Pay per result.** Billed per OTI row written.
- **No charge on failure.** A run that produces zero rows fails with a message and is not
  billed.
- An unresolved look-up identifier still writes one `error: "not found"` row and is billed as a
  base row.
- **Free Apify plans** run the built-in 10-row sample only.
- The whole registry comes back in a single keyless request, so a full run of every OTI
  finishes in well under a minute. There is no pagination.

### FAQ and troubleshooting

**Do I need an API key?** No. The FMC OTI list is public, with no key and no login.

**Why did my run return zero rows?** No OTI matched the filters. Loosen them or widen the date
and amount ranges. A zero-row run fails and is not billed.

**Why is `license_number` null for some entries?** Non-US NVOCCs may operate bonded without an
FMC license. Those show `is_licensed: false` and a null license number. That is correct, not
missing data.

**Why is `renewal_status` `overdue` for an active OTI?** The entry is still on the active list,
but the renewal date on file has passed. It is a signal to check the license, not proof it has
lapsed.

**Why are some names or countries clearly inconsistent?** A small number of entries carry data
the FMC recorded that way, for example a foreign company with `UNITED STATES` in the nation
field. The Actor reports the source values as they are and does not correct them.

**How current is the data?** The FMC refreshes the OTI list on its own schedule, roughly
monthly. It is not real time.

**Is this an official FMC tool?** No. Independent, not affiliated with the Federal Maritime
Commission. It reads only data the Commission publishes.

### Local development

```bash
bun install
bun test                       # offline: parsing, normalisation, input logic
bun run src/main.ts            # reads storage/key_value_stores/default/INPUT.json
bun scripts/verify-live.mjs    # hits www2.fmc.gov directly, checks every mode
```

# Actor input Schema

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

Caps how many OTI rows this run writes. Starts at 10 for a quick sample; raise it for a full run (the whole registry is about 9,800 entries).

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

Search filters the FMC licensed OTI registry into a list. Look up resolves exact organization or license numbers, or company names, and returns one row per identifier.

## `name` (type: `string`):

Match OTIs whose legal name or a registered trade name (DBA) contains this text.

## `otiType` (type: `string`):

Restrict by intermediary type. NVOCC issues its own bills of lading; an ocean freight forwarder books cargo on behalf of shippers.

## `state` (type: `array`):

Two-letter US state code(s), e.g. CA, NY, TX. Leave empty for all states.

## `country` (type: `array`):

Full country name(s) as they appear in the registry, e.g. CHINA, INDIA, UNITED STATES. Leave empty for all countries.

## `basedIn` (type: `string`):

Filter US-based against foreign-based OTIs.

## `city` (type: `string`):

Match OTIs whose city contains this text.

## `foreignNvoccOnly` (type: `boolean`):

Only non-US NVOCCs. This is the group the FMC advises US parties to check most carefully before doing business.

## `unlicensedOnly` (type: `boolean`):

Only non-US NVOCCs that are registered but hold no FMC license (shown with \*\* in the source). They can lawfully operate only if bonded.

## `suretyCompany` (type: `string`):

Match OTIs whose NVOCC or freight-forwarder bond is written by a surety company containing this text, e.g. "Avalon", "Liberty Mutual".

## `qualifyingIndividual` (type: `string`):

Match OTIs with a named qualifying individual (the person the FMC holds responsible) containing this text.

## `hasBond` (type: `boolean`):

Set to true for OTIs with a bond amount on file, or false for those without one.

## `minBondAmount` (type: `integer`):

Only OTIs whose combined NVOCC + forwarder bond amount is at least this.

## `maxBondAmount` (type: `integer`):

Only OTIs whose combined bond amount is at most this.

## `renewalBefore` (type: `string`):

ISO date (2027-01-01). Only OTIs whose license renewal date is on or before this, for an expiring-license watch list.

## `renewalAfter` (type: `string`):

ISO date. Only OTIs whose license renewal date is on or after this.

## `renewalDueInDays` (type: `integer`):

Only OTIs whose license renewal is due within this many days, including any already overdue.

## `identifiers` (type: `array`):

FMC organization numbers (e.g. 019738), license numbers, or company names. Names match the first OTI whose legal or trade name contains the text; unmatched identifiers come back as an error row.

## Actor input object example

```json
{
  "maxItems": 10,
  "mode": "search",
  "otiType": "any",
  "state": [
    "CA"
  ],
  "basedIn": "any",
  "foreignNvoccOnly": false,
  "unlicensedOnly": false
}
```

# Actor output Schema

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

One dataset row per OTI (or per look-up identifier), with organization and license number, OTI type, license renewal date and status, surety bond company and amount, qualifying individuals, trade names, country and address.

# 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 = {
    "maxItems": 10,
    "mode": "search",
    "state": [
        "CA"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("normdata/fmc-oti-scraper").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 = {
    "maxItems": 10,
    "mode": "search",
    "state": ["CA"],
}

# Run the Actor and wait for it to finish
run = client.actor("normdata/fmc-oti-scraper").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 '{
  "maxItems": 10,
  "mode": "search",
  "state": [
    "CA"
  ]
}' |
apify call normdata/fmc-oti-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,normdata/fmc-oti-scraper"
        }
    }
}
```

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/E2hDI5hN4OClZzGpy/builds/qjNRkHaouvgseacNk/openapi.json
