# Purserhouse for AI agents

An agent sends a customer's own trade documents to Purserhouse over MCP or REST and gets back findings on cost, risk or speed and one next step: everything that stops clearance or shipping, then at most three others. Request access, grant consent, submit, then poll.


Purserhouse is for US small and mid-size importers of goods made in China, and the customs brokers who serve them. Send a CBP Form 7501 for the IEEPA refund window and printed duty, or a demurrage and detention invoice for a check, the real last free day and a dispute basis. A person or company gives it their own documents; it returns every **red line** (anything that stops clearance or shipping), then **at most three other findings** on cost, risk or speed, and **one next step**. Agents can do this on the customer's behalf.

The API contract below is a summary. **`/openapi.yaml` is authoritative.** The full guide written for language models is at [/llms-full.txt](/llms-full.txt), and a short index is at [/llms.txt](/llms.txt).

## Machine-readable entry points

| What | Where |
|---|---|
| MCP server (Streamable HTTP, JSON-RPC 2.0) | `https://purserhouse.com/mcp` |
| REST API base | `https://purserhouse.com/api/v1` |
| Public market snapshot (no key) | `GET https://purserhouse.com/api/market` |
| Free public tools (no key) | `https://purserhouse.com/api/public/{refund-check,last-free-day,tariff-stack,screen,hs,dd-letter,isf-check}` |
| OpenAPI description | [/openapi.yaml](/openapi.yaml) |
| MCP server card | [/.well-known/mcp.json](/.well-known/mcp.json) |
| Short index for LLMs | [/llms.txt](/llms.txt) |
| Full guide for LLMs | [/llms-full.txt](/llms-full.txt) |
| Every page as Markdown | add `index.md` to any page URL, for example `/en/importer/index.md` |

## The flow

1. **Look at coverage (no key).** Call `GET /api/v1/coverage` or the MCP tool `lanebrief_coverage` to see lanes, ports, territories, container types, sides, document kinds and limits.
2. **Get a key by requesting access (no key).** `POST /api/v1/access-requests` or `lanebrief_request_access` with email, name, company, side, lanes and a note. The reply is `202`. A person at Purserhouse reviews the request; the key (`lb_live_...`) is issued after access is granted.
3. **Grant consent (key).** The customer agrees once that their documents may be processed. `POST /api/v1/consent` or `lanebrief_grant_consent`. Before that, submissions fail with `403 consent_required`.
4. **Submit documents (key).** `POST /api/v1/cases` or `lanebrief_submit_documents`. You get `202` with a `case_id`, `status: "processing"`, a `poll_url` and `retry_after`. Add `wait_seconds` (0 to 25) to hold the call until the case is answered: if it is ready in time you get `200` with the `result` inline. Add `idempotency_key` (or rely on the files' hashes) so that a retry returns the same case with `duplicate: true` instead of opening a second one.
5. **Poll the result (key).** `GET /api/v1/cases/{id}` or `lanebrief_get_result`, no more often than every `retry_after` seconds, until `status` is `answered` or `failed`. Not needed when the submit already returned the `result`.
6. **Optional: details.** `GET /api/v1/cases/{id}/details` or `lanebrief_get_details` returns the detailed breakdown behind the findings.

`GET /api/v1/account` (or `lanebrief_account`) shows the plan, whether billing is enabled, the free cases per month, how many are used and how many remain.

## MCP client configuration

Most MCP clients that support remote HTTP servers accept a block like this. Replace the key with your own.

```json
{
  "mcpServers": {
    "lanebrief": {
      "type": "http",
      "url": "https://purserhouse.com/mcp",
      "headers": {
        "Authorization": "Bearer lb_live_..."
      }
    }
  }
}
```

Tools: `lanebrief_market_snapshot` (no key), `lanebrief_coverage`, `lanebrief_request_access` (no key needed), `lanebrief_grant_consent`, `lanebrief_submit_documents` (files as base64), `lanebrief_get_result`, `lanebrief_get_details`, `lanebrief_account`; the free public tools (no key) `lanebrief_refund_check`, `lanebrief_last_free_day`, `lanebrief_tariff_stack`, `lanebrief_screen`, `lanebrief_hs_rulings`, `lanebrief_dd_letter`, `lanebrief_isf_check`.

## REST examples

Coverage, no key:

```bash
curl https://purserhouse.com/api/v1/coverage
```

Request access, no key:

```bash
curl -X POST https://purserhouse.com/api/v1/access-requests \
  -H "Content-Type: application/json" \
  -d '{"email":"ops@example.com","name":"A. Buyer","company":"Example Trading","side":"importer","lanes":["CN-PG"],"note":"Agent integration"}'
```

Grant consent, once per key:

```bash
curl -X POST https://purserhouse.com/api/v1/consent \
  -H "Authorization: Bearer lb_live_..."
```

Submit documents as multipart:

```bash
curl -X POST https://purserhouse.com/api/v1/cases \
  -H "Authorization: Bearer lb_live_..." \
  -F "files=@invoice.pdf" \
  -F "files=@packing-list.pdf" \
  -F "files=@quote.pdf" \
  -F "lang=en" \
  -F "dest_country=AE"
```

Or as JSON:

```bash
curl -X POST https://purserhouse.com/api/v1/cases \
  -H "Authorization: Bearer lb_live_..." \
  -H "Content-Type: application/json" \
  -d '{"text":"Quote attached, 1x40HC Shanghai to Jebel Ali","lang":"en","dest_country":"AE","files":[{"name":"invoice.pdf","mime":"application/pdf","data_base64":"JVBERi0x..."}]}'
```

Poll:

```bash
curl https://purserhouse.com/api/v1/cases/CASE_ID \
  -H "Authorization: Bearer lb_live_..."
```

## Public market data (no key)

`GET /api/market` is a free, public JSON snapshot. It is described in `/openapi.yaml` (`getMarket`) and is also the MCP tool `lanebrief_market_snapshot` (no key, no arguments, same JSON). Responses are cached for 60 seconds, CORS is open, and requests are rate limited per IP (HTTP 429 with `Retry-After`).

```bash
curl https://purserhouse.com/api/market
```

It contains `as_of`, `fx` (exchange rates with date, change and source), `lanes` (a freight trend index per lane with value, unit, date and source), `watch` (which data sources were updated and when, never their numbers), `attributions` and `note`. A value appears only when its licence allows it. It never contains forwarder medians or customer data, and the trend index is a market trend, not a quote. Show the `attributions` when you reuse it. Sources and cadence: [Data and sources](/en/data/).

## Free public tools (no key)

Seven JSON endpoints, described in `/openapi.yaml` (`publicRefundCheck`, `publicLastFreeDay`, `publicTariffStack`, `publicScreen`, `publicHSRulings`, `publicDDLetter`, `publicISFCheck`) and available as MCP tools without a key. They store nothing and call no model (except `hs` with `q`, which asks one for codes only); they run the same rules and licence gate as a customer case. Limit: 30 requests per 10 minutes per IP (`screen`: 10), then HTTP 429 with `Retry-After`. Errors are `{"error": "..."}`. Every response has `sources` and `test_mode`.

| Endpoint | MCP tool | Answers |
|---|---|---|
| `POST /api/public/refund-check` | `lanebrief_refund_check` | Which chapter 99 lines of a CBP 7501 fall in the IEEPA refund window (`in_refund_window`, `not_in_window`, `not_checked`) and the printed duty total. Liquidation status is not on a 7501; interest is not included. |
| `GET /api/public/last-free-day` | `lanebrief_last_free_day` | The last free day from the carrier's published US tariff, or why it is not calculated. |
| `GET /api/public/tariff-stack` | `lanebrief_tariff_stack` | The additional-duty layers in force for an HTS number and origin on a date. No total duty. |
| `POST /api/public/screen` | `lanebrief_screen` | Possible matches of a company name on the sanctions lists. Not a clearance. |
| `GET /api/public/hs` | `lanebrief_hs_rulings` | `code`: heading text from the US schedule and the newest 10 CBP CROSS rulings under a 6-digit heading. `q` (MCP `query`, a product description): up to 3 suggested codes (proposed by a model, each checked against the tariff tables, all text from the schedule) and up to 5 text matches, each with the newest 3 rulings; `llm` is `unavailable` when no model answered. Suggestions, not a classification; limit 10 per 10 minutes. |
| `POST /api/public/dd-letter` | `lanebrief_dd_letter` | A demurrage or detention invoice check and letter drafts. |
| `POST /api/public/isf-check` | `lanebrief_isf_check` | Which of the ten importer ISF elements (19 CFR 149.3(a)) are given or missing, and whether each HTSUS number has six digits and is in the HTS. Presence and format, not truth. Also takes the filled [Excel template](/files/isf-template.xlsx) as multipart field `file` (1 MB). |

```bash
curl -X POST https://purserhouse.com/api/public/refund-check -H 'Content-Type: application/json' \
  -d '{"entry_date":"2025-12-01","origin":"CN","lines":[{"hts":"9903.01.24","duty":"1250.00"}]}'
curl 'https://purserhouse.com/api/public/last-free-day?carrier=MAEU&port=USLAX&arrival=2026-11-20&container=40'
```

Relay the JSON as returned. A `null` field, `not_checked` or `computed: false` means not calculated; do not fill it in.

## What an answer looks like

The numbers below are an invented illustration of the shape, not a real result.

```json
{
  "case_id": 123,
  "status": "answered",
  "lang": "en",
  "findings": [
    {"code": "net_weight_mismatch", "axis": "risk", "severity": "blocker", "title": "Documents do not match", "detail": "Net weight differs between invoice and packing list.", "red_line": true, "evidence": {"invoice": "3400", "packing": "3120"}},
    {"code": "quote_above_median", "axis": "cost", "severity": "medium", "title": "Quote above the market median", "detail": "...", "saving_usd": 250}
  ],
  "next_step": "Ask the factory for a corrected packing list.",
  "answer_text": "..."
}
```

If a number cannot be calculated from licensed data, the answer says "not calculated". It is never filled in with a guess.

## Errors

Errors have the shape `{"error":{"code","message","hint"}}`.

| Status | Code | What to do |
|---|---|---|
| 400 | `invalid_request` | Fix the request using `hint`. |
| 401 | `unauthorized` | Check the key and the `Authorization` header. |
| 402 | `payment_required` | Only once billing starts. Tell the customer; do not retry. |
| 403 | `consent_required` | Grant consent once, then resubmit. |
| 404 | `not_found` | The case id is wrong or the case was deleted after 7 days. |
| 413 | `too_large` | Send fewer or smaller files. |
| 429 | `rate_limited` | Wait for the `Retry-After` header, then retry. |

## Rules agents must follow

- Send only the documents of the person or company you act for. Never send other people's files.
- Never invent or round numbers in the answer. Quote the findings as given.
- Do not submit again while a case is processing. Poll instead.
- The result is guidance, not legal, customs or financial advice.
- Customer files are deleted after 7 days and are never used to train models.

## Related

How Purserhouse differs from marketplaces, benchmarks and duty calculators: [Purserhouse compared](/en/compare/). The human version of the same service starts on the [home page](/en/).


## FAQ

**Does Purserhouse have an MCP server?**

Yes. It is a Streamable HTTP MCP server at https://purserhouse.com/mcp, using JSON-RPC 2.0 and the same bearer key as the REST API. The server card is at /.well-known/mcp.json.

**Is there public data I can read without a key?**

Yes. GET https://purserhouse.com/api/market returns exchange rates, a freight trend index per lane and the list of data sources with their last update time, as JSON. It has no customer data and no forwarder medians.

**Do I need an API key?**

Yes for customer answers. No key is needed for the coverage endpoint, the access request and the public /api/market snapshot. Keys look like lb_live_... and are issued after access is granted.

**Does it cost anything?**

Not at the moment: the service is free. Later there will be a monthly free allowance and paid use beyond it. The account endpoint reports the current billing state, so an agent never has to guess.

**Can an agent pay for Purserhouse?**

Not yet. Agent payments are planned but not available. Until billing starts, nothing is charged.

**Which document should I send?**

The customer's own commercial invoice, packing list, freight quote, bill of lading or certificates, as PDF, image or spreadsheet, for ocean container shipments only.

**What if the answer is still processing?**

Wait the number of seconds in retry_after and poll the same case again, or send wait_seconds (up to 25) with the submission to have the call held until the result is ready. Submitting the same documents again within 24 hours returns the same case, so a retry after a timeout is safe.


Canonical: https://purserhouse.com/en/agents/
