Use FIRMSapi from AI agents (MCP)
FIRMSapi runs a remote Model Context Protocol (MCP) server, so an AI agent can look up, search and validate FIRMS codes as tools. Each tool wraps one REST endpoint: same key, same data, same answers, and each tool call counts exactly like the REST call it wraps.
https://api.firmsapi.com/mcp- Transport: Streamable HTTP. Replies are plain JSON (no server-sent events) and there are no sessions.
- Auth: your FIRMSapi key in the
x-api-keyheader, or asAuthorization: Bearer <key>for clients that can only send a bearer token. Keys come from the developer portal. The server checks the key exactly as the REST API does and does not keep it. - Test first: a
fapi_test_…key returns fixture data and is never counted. Switch tofapi_live_…when the agent works.
| Tool | Wraps | What it does |
|---|---|---|
lookup_firms_code |
GET /v1/firms/{code} |
One code’s record: name, status, type, address, port, and the other codes at the same address |
search_firms_facilities |
GET /v1/search |
Find codes from a facility name and/or address, typo-tolerant, with a 0-100 score |
find_nearby_firms_facilities |
GET /v1/nearby |
Facilities near a point, another FIRMS code or a US street address |
validate_firms_code |
POST /v1/validate |
Pre-filing checks for a port and intended use, with checks_passed, checks_failed and checks_not_possible |
list_cbp_ports |
GET /v1/ports |
Every CBP port that has FIRMS codes, with counts |
get_cbp_port |
GET /v1/ports/{port} |
One port and a page of its facilities |
Tool results carry the REST response body as JSON text (and as structuredContent for clients on MCP 2025-06-18
or later), meta included, so the agent sees meta.data_as_of and meta.mode on every answer. Batch, the changes
feed and watchlists are not MCP tools; use the REST API for those.
What the tools can’t do
Section titled “What the tools can’t do”Tell your agent, and your users, where the answers stop:
- FIRMSapi has no connection to ACE. It does not file, submit or check entries. A passing
validate_firms_code(valid: true) means no check against the published CBP FIRMS list failed; it is not ACE acceptance.checks_not_possiblelists what the list cannot show (bond status, FTZ zone ID, direct delivery and port clusters). Confirm those, and the code, in ACE before you file. - Test keys return fixture data: a small sample of real FIRMS rows with approximate coordinates. Never file with it.
- Webhooks and change notifications are not available, over MCP or REST. To see what changed, poll the changes feed (Pro and Enterprise).
- Data is as fresh as the CBP list, which
meta.data_as_ofdates.
Set up your client
Section titled “Set up your client”Replace fapi_test_your_key_here with your key, or better, keep the key in an environment variable as shown.
claude mcp add --transport http firmsapi https://api.firmsapi.com/mcp --header "x-api-key: $FIRMSAPI_KEY"Or share it with your team in the project’s .mcp.json. Claude Code expands ${FIRMSAPI_KEY} from each
person’s environment, so no key goes into git:
{ "mcpServers": { "firmsapi": { "type": "http", "url": "https://api.firmsapi.com/mcp", "headers": { "x-api-key": "${FIRMSAPI_KEY}" } } }}In Customize > Connectors, choose Add custom connector and enter https://api.firmsapi.com/mcp as the server URL.
Set Authentication to No sign-in, then under Request headers add x-api-key with your key as the
value. On Team and Enterprise plans an Owner adds the connector under Organization settings > Connectors.
Request headers are in beta and not every organization has them yet. If you don’t see them, run the server in
Claude Desktop through the mcp-remote bridge (needs Node.js):
{ "mcpServers": { "firmsapi": { "command": "npx", "args": [ "-y", "mcp-remote", "https://api.firmsapi.com/mcp", "--header", "x-api-key:${FIRMSAPI_KEY}" ], "env": { "FIRMSAPI_KEY": "fapi_test_your_key_here" } } }}Add the server to ~/.cursor/mcp.json (all projects) or .cursor/mcp.json (one project). Cursor fills in
${env:FIRMSAPI_KEY} from its environment; restart Cursor after you export the variable.
{ "mcpServers": { "firmsapi": { "url": "https://api.firmsapi.com/mcp", "headers": { "x-api-key": "${env:FIRMSAPI_KEY}" } } }}As of October 2026, ChatGPT’s custom connectors (developer mode) sign in with OAuth or connect without authentication; they can’t send an API key, and FIRMSapi doesn’t offer OAuth sign-in yet, so you can’t add FIRMSapi in the ChatGPT app today. From the OpenAI API, the Responses API’s remote MCP tool sends your key as a bearer token:
import osfrom openai import OpenAI
client = OpenAI()response = client.responses.create( model="YOUR_MODEL", # any model that supports remote MCP tools tools=[{ "type": "mcp", "server_label": "firmsapi", "server_url": "https://api.firmsapi.com/mcp", "authorization": os.environ["FIRMSAPI_KEY"], "require_approval": "never", }], input="Is FIRMS code E430 active, and which port is it in?",)print(response.output_text)The Messages API’s MCP connector sends authorization_token as a bearer token:
import osimport anthropic
client = anthropic.Anthropic()response = client.beta.messages.create( model="claude-opus-5-5", max_tokens=16000, betas=["mcp-client-2025-11-20"], mcp_servers=[{ "type": "url", "url": "https://api.firmsapi.com/mcp", "name": "firmsapi", "authorization_token": os.environ["FIRMSAPI_KEY"], }], tools=[{"type": "mcp_toolset", "mcp_server_name": "firmsapi"}], messages=[{"role": "user", "content": "Find the FIRMS code for Maher Terminal in Elizabeth, NJ."}],)for block in response.content: if block.type == "text": print(block.text)Any other MCP client that speaks Streamable HTTP and can send a header works the same way: the URL above plus
x-api-key.
Try it
Section titled “Try it”Ask your agent something like:
- “Find the FIRMS code for Maher Terminal in Elizabeth, NJ, and check it for a warehouse entry at port 4601.”
- “Which bonded warehouses are within 5 km of 231 Tyler St, Newark, NJ?”
- “Is FIRMS code E005 still active? What other codes share its address?”
Counting and errors
Section titled “Counting and errors”- Counted: each tool call on a live key is one call, from your plan’s quota, then call packs, then capped
metered overage, exactly as in usage and quotas. A
not_foundlookup and a failed validation (valid: false) are answers, so they count. - Not counted: test keys, invalid arguments (
invalid_request), auth errors,quota_exceeded, server errors, and the discovery requests clients make on their own (listing tools, connecting). - Errors are tool results with
isError: trueand the REST error body, so the agent can read the code and fix the call. A missing or wrong key comes back asmissing_api_keyorinvalid_api_keyon the first tool call, not as a failed connection: clients can connect and list the tools without a key. - When you run out of calls, the result is
quota_exceededwith links to buy a call pack or upgrade. - Each result’s
_metahascom.firmsapi/http_status,com.firmsapi/request_id(quote it to support) and, on counted calls,com.firmsapi/quota_limitandcom.firmsapi/quota_remaining.
Protocol details
Section titled “Protocol details”For client authors and registries:
- One endpoint,
POST /mcp, one JSON-RPC request or notification per POST.GETandDELETEreturn405(no standalone event stream, no sessions to end); CORS preflights are answered for browser-based clients. - MCP 2026-07-28 (stateless,
server/discover, request metadata headers checked against the body) and theinitialize-based versions 2025-11-25, 2025-06-18, 2025-03-26 and 2024-11-05. - Capabilities:
toolsonly. All tools are read-only (readOnlyHint: true) and the list does not change between deploys, sotools/listresults are cacheable (cacheScope: public).