# WCS calculator API, v1

Base URL: `https://wcs-import.wcs-export.workers.dev/v1`
This guide online: `https://wcs-import.wcs-export.workers.dev/v1/guide`. Machine-readable spec: `/v1/openapi.json` (point a custom GPT, HubSpot Breeze agent or any tool-calling model at it).

## Keys

Every call carries a key in the header: `Authorization: Bearer wcs_live_…`. Keys in the URL (`?key=`) are refused, since URLs end up in logs and browser history. Keys are issued by WCS and come in two flavours: `wcs_live_` keys price normally and write leads to HubSpot; `wcs_test_` keys price normally and never touch HubSpot. Each key has a daily quota (2,000 calls by default), a partner code that is stamped on every lead it creates, and optional price modifiers.

`GET /v1/me` shows what your key is and which modifiers are in force.

## Where to call the API from

Call it from your server whenever you can. Your web page asks your server for a price, your server calls us with the key, and it passes back only what the page needs. The key never reaches the visitor's browser, and you don't need an iframe or a calculator on the page.

Send the page only the fields it shows. A response can include `published` and `modifiers`, which reveal our rates and your adjustments, so don't forward the whole response if your key has modifiers.

Calling directly from a web page also works, but the key is then visible to anyone who views the page's code. Someone who copies it could use up your daily quota, see your modifiers, or send false leads through `/v1/lead`. WCS can limit a key to your website domains, which stops other websites from using it but doesn't stop scripts or servers, because those send no website origin. If you do call from a page, ask for the domain limit, and consider not calling `/v1/lead` from the page.

If a key may have been exposed, tell us. We can revoke it at once and issue a new one.

## Endpoints

`GET /v1/ports` lists our US departure warehouses (id, name, ZIP).

`GET /v1/destinations` lists every served country with its ports of arrival, which departure warehouses have a lane, and which cargo types.

`GET /v1/resolve?q=Munich` turns free text into a place. US input gives a ZIP and the nearest warehouses; overseas input gives the nearest served port, alternatives in the same country, and whether the country is served.

`GET /v1/ground?zip=60601&port=New%20Jersey&cargo=car` gives the pickup rate from a ZIP to one warehouse (or all, without `port`): open and enclosed, miles, days. Cargo: `car` (default), `ev`, `motorcycle`, `pallet`, `container4` (a 20ft container with 4 cars, on lanes that offer it).

`GET /v1/quote?origin=60601&destination=London&value=50000&enclosed=0` is the full quote. Direction is inferred from the two ends: a US ZIP or city to an overseas place is an export; an overseas postcode or city to a US ZIP or city is an import. A city name resolves on its own (`origin=Munich`); a bare postcode needs its country, either in the text (`origin=80331,%20Germany`, `origin=SW1A%201AA,%20United%20Kingdom`) or as `origin_country=Germany`. Export responses carry every departure warehouse priced (`ports[]`, each with `ground`, `ocean`, `docDays`, `totalCost`, `totalDays`), `selected` (cheapest, or the one you forced with `port=`), `insurance`, and the resolved `origin` and `destination` (with the port chosen and alternatives). `dest_port=Hamburg` forces a port of arrival by name. Import responses carry `ocean` and `air` door to door.

Every quote response also carries a `lead` object: `{ direction, calc, origin, destination, dest_country_id }` for export, `{ direction, calc, origin, destination, origin_country }` for import. Pass those fields straight to `/v1/lead`.

`POST /v1/lead` with a form-encoded or JSON body (`email`, `first`, `last`, `phone`, `direction`, `calc`, `origin`, `destination`, `value`, `dest_country_id` or `origin_country`) records the enquiry, creates or updates the HubSpot contact, assigns the owner (by destination country for export, origin country for import), stamps your partner code, and sends the customer West Coast Shipping's instant-quote email. Send `calc` verbatim from the quote's `lead.calc`: the quote is re-priced on our side and the email then shows the full breakdown (every leg, duties, validity). Without `calc` the email tells the customer the rate will follow personally. `event=used` records the lead without sending the email.

## AI assistants (MCP)

`https://wcs-import.wcs-export.workers.dev/mcp` is a Model Context Protocol server over the same API, for assistants that connect to tools (Claude Code, the Claude API's MCP connector, and other MCP clients that can send a header). It uses the same key in the same header, and each tool call counts against the key's daily quota, with the key's modifiers applied, exactly like the matching `/v1` call. Tools: `quote`, `ground_rate`, `resolve_place`, `list_ports`, `list_destinations`, `whoami`. It never creates leads or emails customers; use `POST /v1/lead` for that.

Claude Code:

```
claude mcp add --transport http wcs https://wcs-import.wcs-export.workers.dev/mcp --header "Authorization: Bearer $KEY"
```

## Modifiers

WCS can attach modifiers to a key. They adjust the published rates for that key only and every adjusted response says so: each departure carries `published` (the unadjusted ground, ocean and total) and the top level carries `modifiers.applied`.

| Modifier | Effect |
|---|---|
| `ocean_add`, `ocean_pct` | plus dollars or percent on ocean freight |
| `ground_add`, `ground_pct` | plus dollars or percent on the ground leg |
| `total_add`, `total_pct` | plus dollars or percent on the total, after the above |
| `flat_ground` | fixed pickup price by ZIP or ZIP prefix, e.g. `{"60601": 300}` for one dealer's lot, `{"606": 400}` for the Chicago area, `{"60601@New Jersey": 300}` for one warehouse only; longest prefix wins, enclosed still multiplies |
| `insurance_off` | never charges insurance |

Negative numbers discount.

## Examples

Ground rate for a dealer in Chicago to our New Jersey warehouse:

```
curl -H "Authorization: Bearer $KEY" "https://wcs-import.wcs-export.workers.dev/v1/ground?zip=60601&port=New%20Jersey"
```

Quote a car from that dealer to a buyer in London, $50,000 declared:

```
curl -H "Authorization: Bearer $KEY" "https://wcs-import.wcs-export.workers.dev/v1/quote?origin=60601&destination=London&value=50000"
```

Log the buyer once they want it (fields from the quote's `lead` object; `calc` URL-encoded):

```
curl -H "Authorization: Bearer $KEY" -X POST "https://wcs-import.wcs-export.workers.dev/v1/lead" --data "email=buyer@example.com&first=Sam&last=Jones&phone=%2B44%207700%20900000&direction=export&origin=Chicago%2060601&destination=Southampton%2C%20United%20Kingdom&value=50000&dest_country_id=12&calc=direction%3Dexport%26origin_zip%3D60601%26dest_country_id%3D12%26dest_city_id%3D8%26cargo_id%3D1%26value%3D50000%26enclosed%3D0"
```

Import, a car in Munich to a buyer in Chicago, $20,000 declared:

```
curl -H "Authorization: Bearer $KEY" "https://wcs-import.wcs-export.workers.dev/v1/quote?origin=80331&origin_country=Germany&destination=60601&value=20000&enclosed=1"
```

The response's `ocean` and `air` blocks are door to door (pickup at the seller, freight, delivery, cargo protection, estimated US duty and fees at the 2.5% base rate); `lead.calc` and `lead.origin_country` go to `/v1/lead` with `direction=import`.

JavaScript:

```js
const API = "https://wcs-import.wcs-export.workers.dev/v1";
const h = { Authorization: "Bearer " + KEY };
const quote = await fetch(`${API}/quote?origin=60601&destination=London&value=50000`, { headers: h }).then(r => r.json());
console.log(quote.selected.portName, quote.selected.totalCost, quote.selected.totalDays);
```

Errors come back as `{ "error": "…" }` with 400 (bad input), 401 (no or unknown key), 403 (revoked), 404 (no served port), 429 (daily quota).
