# Purserhouse: full guide for LLMs and AI agents Purserhouse (https://purserhouse.com) checks the documents of US importers of Chinese goods: a CBP 7501 for the IEEPA refund window and the tariff layers, a demurrage and detention invoice for the last free day and a dispute. This file is self-contained: read it once and you know what the service is, how to call it, and what to say to a customer. The API description at https://purserhouse.com/openapi.yaml is authoritative; if this file and the OpenAPI file disagree, the OpenAPI file wins. ## 1. What Purserhouse is - For US small and mid-size importers of goods made in China, and the customs brokers who serve them (Chinese forwarders and exporters working for US importers can use it in Chinese). - Two ways in, one engine. **Send a CBP Form 7501** (document kind `entry_summary`): which entries and Chapter 99 lines fall inside the IEEPA refund window, the duty printed on them, what to check in ACE (liquidation status, which the 7501 does not show), and the tariff layers for the next shipment with their status. **Send a demurrage and detention invoice** (`dd_invoice`): whether it was computed right against the carrier's published US tariff, the real last free day, the basis for a dispute, and a draft letter. - Inside the engine, not sold separately: document mismatch checks, ISF readiness, sanctions and UFLPA Entity List screening, HTS rulings from CBP CROSS. - Every number has a cited source (CBP, Federal Register, eCFR, USTR, DHS, the carrier's own tariff). The language model only reads documents; it never makes a number. Without a source the answer says "not checked" or "not calculated". - Purserhouse returns every red line (anything that would stop clearance or shipping) first, always all of them, then at most three other findings, each on one axis (cost, risk or speed), and one next step. - It is not a customs broker or a law firm. It does not file in CAPE, file an ISF or an entry, send disputes to carriers, book freight or move cargo. It does not republish carrier or port tariff rates (it applies them to the customer's own documents) and is not a general US tariff or landed-cost calculator: it lists duty layers with their status, not a total, and does not compute refund interest. - Channels: the web, email, Telegram, WhatsApp, and for AI agents the MCP server and REST API described here. The coverage endpoint returns the authoritative list of lanes, ports, container types and document kinds. - Languages: English and Simplified Chinese; ask for the answer language with the `lang` field. Older Arabic pages exist but are no longer updated. ## 2. Concepts - **Case**: one submission of documents. It has a `case_id` and a `status`: `processing`, `answered` or `failed`. - **Finding**: one result with an `axis` (`cost`, `risk` or `speed`), a `severity`, a `title`, a `detail`, `red_line` (true when it stops clearance or shipping), `evidence` (the numbers behind it), `sources` (name, url, attribution and the newest parsed snapshot with `fetched_at` and `sha256`) and, for cost findings where it can be calculated, `saving_usd`. - **Next step**: the single most useful action for the customer. - **Details**: a deeper breakdown behind the findings, fetched separately and only when needed. - **Key**: a bearer credential of the form `lb_live_...`, issued after access is granted. A key belongs to one customer account. - **Consent**: the customer's one-time agreement that their documents may be processed. It is required before the first submission. - **Not calculated**: the wording used when Purserhouse has no licensed source for a number. It is never replaced by an estimate from general knowledge. ## 3. Data rules (what answers are built from) - **Sources:** each source, who publishes it, how often it updates and its licence status is listed at https://purserhouse.com/en/data/ ; that page is authoritative. - **Licence gate:** a licence ledger records each source. A source whose terms have not been reviewed ("unchecked") is loaded but never reaches customer answers or the public data. Do not tell a customer otherwise. - **Carrier and port tariffs:** applied to the customer's own documents and linked, never republished as tables and never given to the language model. - Exchange rates and the FBX trend can appear in answers and on the public market page only when their licence allows it. - A market trend index (for example FBX): direction only. It is never compared with a customer's quote. - The median of at least three forwarders' rate sheets, anonymous. With fewer than three sources Purserhouse says so and leaves rates out. - No public data about the customer is used. - A licence ledger is kept for each data source. - Customer files are deleted after 7 days. Customer data is not used to train models. - Every number is an estimate or a table lookup, not a bookable price or a customs ruling. ## 4. The agent flow, step by step 1. **Coverage (no key).** `GET /api/v1/coverage`, or the MCP tool `lanebrief_coverage`. Check that the customer's lane, side and documents are in scope and read the limits and the `free` flag. 2. **Request access (no key).** `POST /api/v1/access-requests`, or `lanebrief_request_access`, with `email`, `name`, `company`, `side`, `lanes`, `note`. The response is `202`. The request is reviewed by a person; when access is granted a key (`lb_live_...`) is issued, created in the web app or by the operator. An agent cannot create its own key. Tell the customer to expect a reply on the email they gave. 3. **Grant consent (key).** `POST /api/v1/consent`, or `lanebrief_grant_consent`. Only the customer, or an agent explicitly authorised by the customer, should do this. It is needed once; submissions before it fail with `403 consent_required`. 4. **Submit documents (key).** `POST /api/v1/cases`, or `lanebrief_submit_documents`. Response `202`: `{case_id, status: "processing", poll_url, retry_after}`. With `wait_seconds` (0 to 25) the call is held until the case is answered or failed; if it is ready in time the response is `200` and carries `result` (the same body as the poll). Send `idempotency_key` (up to 200 characters) so a retry within 24 hours returns the same case with `duplicate: true`; without a key the hash of the files, text, lang and dest_country is used. 5. **Poll (key).** `GET /api/v1/cases/{id}`, or `lanebrief_get_result`. Wait `retry_after` seconds between polls. Stop when `status` is `answered` or `failed`. 6. **Details (optional, key).** `GET /api/v1/cases/{id}/details`, or `lanebrief_get_details`. 7. **Account (optional, key).** `GET /api/v1/account`, or `lanebrief_account`: plan, billing state and usage. ## 4b. Public market data (no key) `GET https://purserhouse.com/api/market` returns JSON, no key, free to read. Fields: `as_of`, `live`, `fx` (currency, pair, rate, date, change, source), `lanes` (code, name, a trend `index` with value, unit, date, change, source), `watch` (sources that were updated successfully, with kind and last-update time only, never their numbers), `attributions` and `note`. Only exchange rates and the FBX trend carry numbers; some lanes have no index. `watch` also lists sources whose licence is not yet reviewed, by name and time only, and they do not reach customer answers. It is in /openapi.yaml (`getMarket`) and is the MCP tool `lanebrief_market_snapshot` (no key). Cached 60 s, CORS open, rate limited per IP (429 with Retry-After). It never contains forwarder medians or any customer data. A field is omitted when its licence does not allow it. The trend index is a market trend, not a quote. Attribute the sources listed in `attributions` when you reuse the data. Page for people: https://purserhouse.com/en/market/ . Description of the sources: https://purserhouse.com/en/data/ ```bash curl https://purserhouse.com/api/market ``` ## 4c. Free public tools (no key) Seven JSON endpoints under `https://purserhouse.com/api/public/`, no key, nothing stored, no model called (except `hs` with `q`: the model proposes codes only). They run the same rule code as a customer case and the same licence gate. Limit 30 requests per 10 minutes per address (`screen`: 10); HTTP 429 with `Retry-After`. Errors are `{"error": "..."}` with HTTP 400. Every answer carries `sources` (`code`, `name`, `url`) and `test_mode` (true only on a development server that lets unchecked sources answer). A `null` field, `not_checked` or `computed: false` means not calculated: say so, never fill it in. - `POST /api/public/refund-check` `{"entry_date":"2025-12-01","origin":"CN","lines":[{"hts":"9903.01.24","duty":"1250.00"}]}` (up to 50 lines). Per line `status` is `in_refund_window`, `not_in_window` or `not_checked` (the heading or date is not in our list; never read it as "not eligible"), with `duty`, `rate`, `refund_condition`, `refund_phase`, `legal_ref`, `source_url`. `total_in_window` is the sum of the printed duties in the window, or null. The 7501 does not show liquidation status, which also decides eligibility; interest is not included. MCP: `lanebrief_refund_check`. - `GET /api/public/last-free-day?carrier=MAEU&port=USLAX&arrival=2026-11-20&container=40`: `computed`, `last_free_day`, `free_days`, `day_basis`, `count_from`, `skipped_dates`, `not_calculated_reason`, `carrier`, `port`, `section`, `source_url`, `note`. From the carrier's published US tariff; a contract with the carrier may give more. MCP: `lanebrief_last_free_day`. - `GET /api/public/tariff-stack?hts=8504409580&origin=CN&date=2026-10-21`: `base`, `layers` (`code_prefix`, `kind`, `rate_raw`, `pct`, `status`, `valid_from`, `valid_to`, `refund_condition`, `legal_ref`, `source_url`), `order_note` (CBP's reporting order, CSMS 67844987) and `notes`. No total duty is computed; Section 232 layers carry `heading` and `value_basis`, and a number our us_s232 list does not cover is "not checked", never "not subject". MCP: `lanebrief_tariff_stack`. - `POST /api/public/screen` `{"name":"...","country":"CN"}`: `available`, `checked_at`, `lists`, `hits` (`name`, `list`, `programs`, `score`, `source_url`). A hit is a possible match, not a finding; no hit is not a clearance. MCP: `lanebrief_screen`. - `GET /api/public/hs?code=850440`: the newest 10 CBP CROSS rulings under the heading (`ruling`, `date`, `hs`, `summary`, `url`). A precedent, not proof. MCP: `lanebrief_hs_rulings`. - HS lookup: GET /api/public/hs?q= (or ?code=) — up to 3 suggested HTS codes to check (model proposes codes only, checked against the tariff tables), up to 5 matches of the schedule's wording, heading text and CBP CROSS rulings; suggestions, not a classification. MCP: lanebrief_hs_rulings (query). - `POST /api/public/dd-letter` `{"carrier","port","container","arrival_date","charge_kind","charge_from","charge_to","charge_days","amount","currency","invoice_number","free_time_days"?,"demurrage_per_day"?,"lang":"en"|"zh"}`: `findings` and `letters` (dispute, extension, waiver drafts). MCP: `lanebrief_dd_letter`. - POST /api/public/isf-check (MCP lanebrief_isf_check): which of the ten importer ISF elements of 19 CFR 149.3(a) are given or missing, and whether each HTSUS number has six digits and is in the HTS; JSON or the filled template /files/isf-template.xlsx (multipart field file, 1 MB). Presence and format, not truth. Pages for people that wrap these tools: /en/ieepa-refund/calculator/ (refund-check), /en/demurrage/last-free-day/ (last-free-day), /en/tariff/stacking/ (tariff-stack), /en/uflpa/check/ (screen), /en/hs/ (hs), /en/demurrage/letter/ (dd-letter), /en/isf/checklist/ (isf-check). The questions people search and the page that answers each are listed in https://purserhouse.com/llms.txt . ## 5. REST API Base URL: https://purserhouse.com/api/v1. Authentication: `Authorization: Bearer lb_live_...` on every endpoint except coverage and access-requests. ### GET /api/v1/coverage (public) Returns lanes, ports, territories, container types, sides, document kinds, limits and a free flag. ### POST /api/v1/access-requests (public) JSON body: `{email, name, company, side, lanes, note}`; `lanes` is an array of lane codes from coverage (for example `["CN-PG"]`). Response `202`. `side` is one of importer, exporter, forwarder, broker. ### POST /api/v1/consent (key) Records data consent. Required once before the first submission. ### POST /api/v1/cases (key) Either multipart form data with fields `files` (one or more), `text`, `lang`, `dest_country`; or JSON: ```json { "text": "Free text from the customer, for example the question.", "lang": "en", "dest_country": "AE", "files": [{"name": "invoice.pdf", "mime": "application/pdf", "data_base64": "JVBERi0x..."}] } ``` Response `202`: ```json {"case_id": 123, "status": "processing", "poll_url": "/api/v1/cases/123", "retry_after": 15} ``` With `"wait_seconds": 20` and an answer ready in time, `200`: `{"case_id": 123, "status": "answered", "poll_url": "...", "retry_after": 15, "result": {"case_id": 123, "status": "answered", "findings": [...], "next_step": "...", "answer_text": "..."}}`. A retry of an earlier submission adds `"duplicate": true`. ### GET /api/v1/cases/{id} (key) Returns `{case_id, status, lang, created_at, answered_at, findings, next_step, answer_text}`; each finding has `code, axis, severity, title, detail, red_line` and, where present, `evidence`, `sources` (each `{name, url, attribution, latest_snapshot: {fetched_at, sha256}}`; show the attribution when it is required) and, for freight, `saving_usd`. Red lines come first and are never cut; at most three other findings follow. While processing it also returns `retry_after`. `status` is `processing`, `answered` or `failed`. While processing, the other fields may be empty. ### GET /api/v1/cases/{id}/details (key) The detailed breakdown behind the findings. ### GET /api/v1/account (key) Returns the plan, `billing {enabled, free_cases_per_month, used_this_month, remaining}` and usage. ## 6. MCP server - Endpoint: POST https://purserhouse.com/mcp (Streamable HTTP, JSON-RPC 2.0). Same bearer header as REST. Server card: https://purserhouse.com/.well-known/mcp.json. - Client configuration: ```json {"mcpServers":{"lanebrief":{"type":"http","url":"https://purserhouse.com/mcp","headers":{"Authorization":"Bearer lb_live_..."}}}} ``` Tools: | Tool | Needs key | Purpose | |---|---|---| | `lanebrief_coverage` | no | Lanes, ports, territories, container types, sides, document kinds, limits, free flag | | `lanebrief_request_access` | no | Ask for access with email, name, company, side, lanes, note | | `lanebrief_grant_consent` | yes | Record the customer's data consent (once) | | `lanebrief_submit_documents` | yes | Submit a case; files are sent as base64 | | `lanebrief_get_result` | yes | Get the status and answer of a case | | `lanebrief_get_details` | yes | Get the detailed breakdown of an answered case | | `lanebrief_account` | yes | Plan, billing state and usage | | `lanebrief_market_snapshot` | no | Same JSON as /api/market | | `lanebrief_refund_check` | no | Same as POST /api/public/refund-check | | `lanebrief_last_free_day` | no | Same as GET /api/public/last-free-day | | `lanebrief_tariff_stack` | no | Same as GET /api/public/tariff-stack | | `lanebrief_screen` | no | Same as POST /api/public/screen | | `lanebrief_hs_rulings` | no | Same as GET /api/public/hs | | `lanebrief_dd_letter` | no | Same as POST /api/public/dd-letter | | `lanebrief_isf_check` | no | Same as POST /api/public/isf-check | The tools mirror the REST and public endpoints one to one. Read each tool's input schema from the server (`tools/list`) rather than relying on this summary. ## 7. Errors and what to do All errors have the shape `{"error":{"code","message","hint"}}`. Read `hint`; it says what to change. | Status | Code | Meaning | What to do | |---|---|---|---| | 400 | `invalid_request` | Malformed or missing field | Fix the request as the hint says. Do not retry unchanged. | | 401 | `unauthorized` | Missing or wrong key | Check the header and the key. Do not guess keys. Ask the customer. | | 402 | `payment_required` | Free allowance used up (only once billing starts) | Stop and tell the customer. Do not retry. | | 403 | `consent_required` | Consent not yet recorded | Get the customer's agreement, call consent, then resubmit once. | | 403 | `account_paused` | The account is paused by Purserhouse | Stop and tell the customer to contact Purserhouse. | | 404 | `not_found` | Unknown case id, or the case was deleted after 7 days | Check the id. Resubmit only if the customer asks. | | 409 | `not_ready` | Details asked for before the case is answered | Poll the result first; ask for details after `status` is `answered`. | | 413 | `too_large` | Files too big | Send fewer or smaller files; check limits in coverage. | | 429 | `rate_limited` | Too many requests | Wait the number of seconds in the `Retry-After` header, then retry. | A case with `status: "failed"` did not produce an answer. Tell the customer, check the documents are readable and in scope, and resubmit at most once with better input. ## 8. What an answer looks like The values below are invented to show the shape. They are not real results. ```json { "case_id": 123, "status": "answered", "lang": "en", "created_at": "2026-01-01T10:00:00Z", "answered_at": "2026-01-01T10:02:10Z", "findings": [ {"code": "net_weight_mismatch", "axis": "risk", "severity": "blocker", "title": "Documents do not match", "detail": "Net weight differs between the invoice and the packing list.", "red_line": true, "evidence": {"invoice": "3400", "packing": "3120"}}, {"code": "ieepa_refund_eligible", "axis": "cost", "severity": "high", "title": "Entry inside the IEEPA refund window", "detail": "One Chapter 99 line of this entry is inside the refund window; the duty printed on it is in evidence. Check the liquidation status in ACE.", "red_line": false, "evidence": {"lines": ["9903.01.24"], "duty_printed": "1250.00"}} ], "next_step": "Check the liquidation status of the entry in ACE, then file in CAPE or ask your broker to.", "answer_text": "Plain-language summary of the findings and the next step." } ``` When relaying: give the findings in order, then the next step. Use the customer's own language. If a figure is "not calculated", say it is not calculated and why; do not fill it in. ## 9. Limits and pricing - Limits (file size, files per case, rate limits) are returned by the coverage endpoint and by 413 and 429 responses. Treat the coverage response as the source of truth. - The service is free right now. Later: a monthly free allowance, paid beyond it. Agent payments are planned but not available yet. `GET /api/v1/account` reports `billing.enabled`, `free_cases_per_month`, `used_this_month` and `remaining`. - Customer files are deleted after 7 days. After that the case returns 404. ## 10. What NOT to do - Do not invent, round, extrapolate or "fill in" numbers. If Purserhouse says a number is not calculated, relay exactly that. - Do not send other people's data. Send only the documents of the customer you act for, and only after consent. - Do not resubmit while a case is processing. Poll the same case at the `retry_after` interval instead. Resubmitting the same documents within 24 hours returns the same case (`duplicate: true`) and costs nothing, but it adds nothing either. - Do not compare a market trend index figure with the customer's quote. Purserhouse does not, and neither should you. - Do not present the result as legal, customs, tax or financial advice. It is guidance based on official tables and a rate median, not a customs ruling or a bookable price. - Do not use Purserhouse for air freight or for lanes outside coverage. - Do not share or log the bearer key. - Do not claim Purserhouse books freight, files declarations or guarantees clearance. ## 11. How Purserhouse differs from other tools Purserhouse is a checker for documents you already have. Other tools are often the right choice for other jobs. Statements about other companies are category-level. - Duty and landed-cost calculators (for example Flexport's, Zonos, Avalara): known for estimating total duty and landed cost from a code and a value. Purserhouse does not compete there: it lists the layers in force for a code with their legal status and source, and works from the customer's own 7501. - Refund advisers and law firms: known for preparing and filing refund claims. Purserhouse only shows which lines of a 7501 fall in the IEEPA refund window and the duty printed; it does not file in CAPE. - Freight and demurrage audit services: known for auditing invoices at volume as a managed service. Purserhouse checks one invoice against the carrier's published US tariff and 46 CFR 541.6 and drafts the letter; the customer sends it. - Digital forwarders and marketplaces (for example Flexport, Freightos): known for booking and moving cargo. Purserhouse does not sell or move freight. - Plain chat AI: flexible, but may state numbers without a source. Purserhouse answers only from licensed official sources and says "not calculated" when it has none. What is distinct: it works on the customer's own documents; every number has a source and a licence row; it refuses to invent numbers; it is agent-native through MCP and REST. More: https://purserhouse.com/en/compare/ (Markdown: https://purserhouse.com/en/compare/index.md). ## 12. Links - For AI agents (human-readable): https://purserhouse.com/en/agents/ (Markdown: https://purserhouse.com/en/agents/index.md) - OpenAPI: https://purserhouse.com/openapi.yaml - MCP server card: https://purserhouse.com/.well-known/mcp.json - Request access in a browser: https://purserhouse.com/en/request/ - Send a document (no account; emailed link to the result; files deleted after 7 days): https://purserhouse.com/en/send/ , https://purserhouse.com/zh/send/ - Questions people search, and the page that answers each: https://purserhouse.com/llms.txt - Data and sources: https://purserhouse.com/en/data/ - Privacy: https://purserhouse.com/en/privacy/ - Contact: brief@purserhouse.com