FirstVector SignalsStart a 14-day trialStart trial

Developers · JSON feed and MCP endpoint, field dictionary

The same data, in your own tools

For the engineer or analyst who wires it in: every subscriber and trial token works on the JSON feed, and firm licenses and regional charters also get an MCP endpoint, so an assistant or an internal agent can ask for the same filings. Both return the rows your terminal shows: verified, non-excluded WARN filings from Texas, Wisconsin, Illinois, New York, Ohio and Florida, scored by the rule on /methodology, with a notice date in the last 45 days.

Contract version 2026-10-23 · changelog · deprecation policy · status page

What you can do with it: pull the feed into a CRM, a watch-list or a spreadsheet; join it to your own portfolio or borrower list by company name and state; let an assistant that speaks MCP answer "what closed in Ohio this month". What you cannot do with it: redistribute or resell the rows, or share the token outside your firm (terms, sections 8 and 9).

Try it now

Three requests your browser makes to this service as the page opens, answered live and shown exactly as returned, with the time each took. Nothing here is cached for more than a minute, on the server or in your browser.

GET /api/signals?days=14

The public feed, fetched when this page opens (needs JavaScript; without it, the same response as rendered on the server when this page loaded, cut to its first rows, is under Example responses).

This block fills in when your browser fetches /api/signals?days=14.

POST /mcp · tools/list

The MCP endpoint, posted to from this page without a token (needs JavaScript).

This block fills in when your browser posts {"jsonrpc":"2.0","id":2,"method":"tools/list"} to /mcp.

This page carries no token, so the endpoint answers with the public window, exactly what the feed serves without a token: the tool list above is the real one, and a tools/call for list_warn_filings from here returns the masked public rows (14 days, 25 rows, access "public" and a note); your firm token returns the full rows (the MCP section).

GET /health

Build: commit 04460f196c5b · deployed 2026-10-24T01:07:46Z · 756 tests passed in that build · last end-to-end self-test pass 2026-10-24T01:14:13.899378+00:00

That line is rendered on the server from the same values and replaced by the live answer of /health when the page opens. The nightly runs, per state, and the self-test history are on /status.

Quick start

What a client README would hold, in one place: the feed from a shell and from Python with nothing but the standard library, the two spreadsheets as they really work today, and one MCP session request by request. FV_TOKEN is your terminal token (Your token).

JSON feed from a shell

# the subscriber feed: every field, 45 days by notice date, every covered state
curl -s "https://firstvectorsignals.com/api/signals?token=$FV_TOKEN&days=45" | python3 -m json.tool

# the same URL without a token: the public preview (14 days, 25 masked rows), still HTTP 200
curl -s "https://firstvectorsignals.com/api/signals?days=14"

# was the token accepted? "full" is true; otherwise "access" says why not
curl -s "https://firstvectorsignals.com/api/signals?token=$FV_TOKEN&days=45" \
  | python3 -c 'import json,sys; d=json.load(sys.stdin); print(d["count"], d["full"], d["access"])'

Python, standard library only

import json, os, urllib.parse, urllib.request
from datetime import datetime, timezone

BASE = "https://firstvectorsignals.com"
query = urllib.parse.urlencode({"token": os.environ["FV_TOKEN"], "days": 45, "states": "TX,OH"})
with urllib.request.urlopen(f"{BASE}/api/signals?{query}", timeout=60) as r:
    feed = json.load(r)

if not feed["full"]:                      # the token was not accepted: these are the masked public rows
    raise SystemExit(f"masked response, access={feed['access']}: {feed.get('note')}")
for f in feed["filings"]:                 # best score first, then newest notice date
    print(f["score"], f["state"], f["notice_date"], f["company"], f["workers"], f["source_url"])

# new since your previous pull: first_seen_at, the same test the morning digest makes
since = datetime(2026, 10, 22, 7, tzinfo=timezone.utc)
new = [f for f in feed["filings"] if datetime.fromisoformat(f["first_seen_at"]) > since]
print(len(new), "new since", since.isoformat())

Spreadsheets

Excel (Get & Transform, also called Power Query): Data › From Web, paste https://firstvectorsignals.com/api/signals?token=YOUR_TOKEN&days=45 and press OK. Excel reads the JSON and opens the query editor on the envelope: next to filings choose List, then To Table, then the expand icon on the column to turn each filing's fields into columns, then Close & Load. Data › Refresh All pulls again. The URL with your token is saved in the workbook's query, so keep the file inside your firm.

Google Sheets has no built-in JSON import: IMPORTDATA reads CSV or TSV, so it cannot read the feed. What it can read is the terminal's CSV export, which any token with full access has (the same 45-day window, every feed column): =IMPORTDATA("https://firstvectorsignals.com/terminal/YOUR_TOKEN/export.csv"). Sheets refreshes that formula on its own schedule; every refresh counts as a pull on your seat, and the token sits in the sheet, so share it only inside your firm. The watchlist export, /terminal/YOUR_TOKEN/watchlist.csv, imports the same way and is not counted as a pull. We have written no add-in or script for either spreadsheet; these are their own import features.

MCP, one session

The requests in the order a client makes them, each a JSON-RPC 2.0 body posted to /mcp?token=…; the bodies are exact and the endpoint answers them as shown. A firm-license or charter token returns the full rows; without a token, or with one we do not know, the same requests answer with the public window (masked rows, 14 days, 25 rows). The MCP section explains the tools and what a token without full access gets.

MCP="https://firstvectorsignals.com/mcp?token=$FV_TOKEN"
H='Content-Type: application/json'

# 1. initialize
curl -s -X POST "$MCP" -H "$H" -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"your-client","version":"1.0"}}}'
# -> {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{"tools":{}},"serverInfo":{"name":"firstvector-signals","version":"<the version /health reports>"}}}

# 2. the initialized notification (a client sends it after initialize; answered with HTTP 204 and no body)
curl -s -X POST "$MCP" -H "$H" -d '{"jsonrpc":"2.0","method":"notifications/initialized"}'

# 3. tools/list
curl -s -X POST "$MCP" -H "$H" -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
# -> {"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"get_firstvector_status",...},{"name":"list_warn_filings",...},{"name":"run_morning_job",...}]}}

# 4. tools/call: Ohio and Illinois, last 30 days by notice date, top 20 by score
curl -s -X POST "$MCP" -H "$H" -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"list_warn_filings","arguments":{"days":30,"states":["OH","IL"],"limit":20}}}'
# -> {"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"{\"filings\": [ ... ]}"}],"isError":false}}
#    result.content[0].text is a JSON string: parse it for {"filings": [...]} with the subscriber feed's fields

# 5. tools/call: the service status
curl -s -X POST "$MCP" -H "$H" -d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"get_firstvector_status","arguments":{}}}'
# -> ... "text":"{\"filings_30d\": <integer>, \"filings_24h\": <integer>, \"last_ingest\": \"<UTC date and time>\"}" ...

The same session from Python:

import json, os, urllib.request

MCP = "https://firstvectorsignals.com/mcp?token=" + os.environ["FV_TOKEN"]

def rpc(body: dict) -> dict:
    req = urllib.request.Request(MCP, data=json.dumps(body).encode(), headers={"Content-Type": "application/json"}, method="POST")
    with urllib.request.urlopen(req, timeout=60) as r:
        return json.load(r)

print(rpc({"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": {"name": "your-client", "version": "1.0"}}})["result"]["serverInfo"])
print([t["name"] for t in rpc({"jsonrpc": "2.0", "id": 2, "method": "tools/list"})["result"]["tools"]])
call = rpc({"jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": {"name": "list_warn_filings", "arguments": {"days": 30, "states": ["OH", "IL"], "limit": 20}}})
filings = json.loads(call["result"]["content"][0]["text"])["filings"]   # the text is a JSON string
for f in filings:
    print(f["score"], f["state"], f["notice_date"], f["company"], f["workers"])

Limits, exactly as enforced today

  • Per client address: 60 requests per minute to /api/signals; 60 requests per minute to /mcp; 120 requests per minute to the one-click links in our emails; 300 requests per minute to every other public page; 5 requests per hour to the trial form (its submissions). Over a limit the service answers HTTP 429 with a Retry-After header (seconds) and does not serve the request. The count is kept in memory by each server process, so an address that reaches several processes can see somewhat more than the number, and everyone behind one office gateway shares one address. /health is not limited.
  • Per token: nothing is enforced. Each unmasked pull is counted per calendar month and the count is shown in your terminal's account strip; nothing is refused on it.
  • Per request: days is clamped to 1–45 with a token and 1–14 without; rows are capped at 300 (25 without a token) and the MCP limit argument at 1–300 (25); a database query is given 30 seconds.
  • Per instance: the service runs on Cloud Run with 20 concurrent requests per instance and at most 10 instances; the public (no-token) feed response is cached for about a minute per server process.
  • Fair use: email us before you plan more than a few hundred calls a day; if a token is hammering the endpoint we may add a limit per token, and we tell you before we do. Today the only limits are the per-address ones above.

Your token

There is no separate API key. Your terminal token is the credential for the feed and for MCP: the last part of your terminal link, a random string of 20 to 64 letters, digits, hyphens and underscores, sent as the token query parameter. Treat it like a password: it travels in the URL, so it can appear in request logs on our side and in any proxy on yours. If it leaks, email us and we retire it and issue a new one; there is no self-service rotation.

JSON feed

GET https://firstvectorsignals.com/api/signals?token=YOUR_TOKEN&days=45&states=TX,OH

GET only; the response is application/json. No request body and no headers are required. The endpoint sends no CORS headers, so call it from a server, a script or a spreadsheet connector rather than from browser JavaScript on another site.

ParameterWhat it does
tokenOptional. With a valid token you get the subscriber feed (up to 45 days, up to 300 rows, every field below except source_record). Without one, or with a token we do not recognise, the same URL returns the public preview (14 days, 25 rows, masked fields) with HTTP 200, not an error. Check "full": true in the response to confirm the token was accepted. A paused token still works; a token whose trial or subscription has ended gets the masked rows with "access": "expired".
daysOptional, default 14. How far back to look, by the filing's notice date (not when the state posted it or when we pulled it). Clamped to 1–45 with a token and 1–14 without; the value actually used is echoed back as days. A non-integer value returns HTTP 422.
statesOptional. Comma-separated codes from TX, WI, IL, NY, OH, FL; case does not matter. Unknown codes are dropped; asking only for states we do not cover returns count: 0 rather than every state. Omit it for all six.
marketsOptional. Comma-separated market codes, U.S. states and countries alike (the codes above; case does not matter). With it, filings carries the U.S. states named (none when no state is named; with states too, only the states named by both) and uk_notices is present only when UK is named and the United Kingdom is live; the envelope echoes the codes applied as markets. Codes that are not live are ignored. Without it the response is exactly what it was before the parameter existed: every covered state, and the UK list whenever the United Kingdom is live. Codes and signals: Markets and signals.

There is no limit, paging or sort parameter. Rows come back ordered by score (highest first), then notice date (newest first), capped at 300 (25 public). The envelope carries count, days, states, full, access (public, full, expired or payment_needed), filings and, on a masked response, note.

# everything in the 45-day window
curl -s "https://firstvectorsignals.com/api/signals?token=$FV_TOKEN&days=45"

# Ohio and Illinois only, last 7 days by notice date
curl -s "https://firstvectorsignals.com/api/signals?token=$FV_TOKEN&days=7&states=OH,IL"

# what is new since your previous pull: compare first_seen_at, as the digest does
curl -s "https://firstvectorsignals.com/api/signals?token=$FV_TOKEN&days=45" \
  | jq --arg t "2026-10-22T07:00:00+00:00" '.filings[] | select(.first_seen_at > $t)'

Field dictionary

Every row is one WARN filing as one state published it. Subscriber responses carry every column below except source_record, in this order. An empty string means the state left the field blank or does not publish it; null means no date. The table is generated from the published contract (fv/api_schema.json, version 2026-10-23), which names every served column with its type and is checked against a live response before every deploy; a new column appears here, and in the changelog, when it is added.

Machine-readable: GET /api/schema.json serves the contract file itself, verbatim, as application/json (version 2026-10-23). The dictionary below is rendered from that file, so the two cannot disagree.

FieldTypePublicMeaning
filing_idstringyes24 hex characters. A stable key: SHA-256 of state + company (lower-cased) + notice date + site (address, else city). It is deliberately not built from the head count, so when a state revises a count the same filing_id is updated in place.
statestringyesTX, WI, IL, NY or OH today; a new state appears as a new value, never as a new field. FL (Florida) was added on 2026-10-23.
companystringThe name exactly as the state filed it, with only runs of whitespace collapsed. Texas and Illinois publish the site or location name (TWC JOB_SITE_NAME, DCEO Location Name); Wisconsin, New York (Business Legal Name) and Ohio publish the employer's name. Florida publishes the employer's name, as filed and with any d/b/a, in bold at the top of its Company Name cell.
dbastring"Doing business as" name. Illinois only; empty for the other four states. Empty for Florida too.
citystringAs filed for TX, WI, IL and OH (Ohio writes "Unknown" for some rows; we store that as empty). New York publishes a single site-address field and no city column, so the New York city is read out of that address. Florida writes the address under the company name, ending CITY, FL, ZIP: the city is read from that last line, in capitals as the state writes it.
countystringAs filed where the state publishes one; empty otherwise.
addressstringStreet address. Illinois (Location Address) and New York (Impacted Site Address, verbatim). Empty for TX, WI and OH. Florida (the lines under the company name, joined with commas, ending with the city, state and ZIP).
workersintegerInteger head count from the state's own column (TX TOTAL_LAYOFF_NUMBER, WI Affected Workers, IL Revised Layoff when the state has revised it, else Expected Layoff, NY Number of Affected Workers, OH Potential Number Affected). Verified rows are always between 1 and 100,000. Florida Employees Affected, read only when the cell is a whole number.
notice_datedateyesYYYY-MM-DD. The date the state records for the notice (TX NOTICE_DATE, WI Notice Received, IL Initial Date Reported, NY Date of WARN Notice, OH Date Received). The days window filters on this field. Florida State Notification Date (the day the state was notified, written MM-DD-YY by the state).
effective_datedate or nullYYYY-MM-DD or null. The layoff or closure start date where the state publishes one (TX, WI, NY, OH). Always null for Illinois, whose report has no such column. Florida publishes a range, start thru end (equal for a single day): the start is stored and the end stays in the raw record.
notice_typestringyesClosure versus layoff as the state words it: WI "Closure" or "Workforce Reduction" (decoded from the state's CL/WR codes), IL "Plant Closure", "Mass Layoff" or "Layoff", NY and OH the state's own wording. Always empty for Texas. The score treats any value containing "clos" as a closure. Florida publishes none, so it is empty as for Texas.
reasonstringIllinois: the state's Causes column, else its Status. New York: permanent-or-temporary plus the state's reason. Empty elsewhere.
industrystringWisconsin's NAICS description and Illinois's Industry column. Empty for TX, NY and OH. Florida: the name of its Industry sector (a NAICS sector name; Florida publishes no code).
naicsstringIllinois only, as the state writes it. Empty elsewhere.
sourcestringThe name of the state publication the row came from.
source_urlstringWhere the filing is published: the state's WARN listing page for TX, WI, IL and NY; the per-filing PDF on the state's asset host for Ohio (the listing page when Ohio gives no usable PDF link). Florida rows: the Rapid Response System listing of the notice's year (the employer's notice PDF is served only through a form, so there is no per-notice link).
source_recordtext, never servedNever served. The raw state row (JSON, up to 4,000 characters) kept with every filing so any figure can be traced to the state's file. Email us if you need to see one.
scoreintegeryesInteger 0-100 from the fixed rule on /methodology.
score_breakdownstringThe components that produced the score, separated by semicolons, for example "workers 50-149 (+30); plant/site closure (+25); filed within 45 days (+12)". Recency points are computed on the night of the pull, so a row's score can drop as it ages.
verifiedbooleanAlways true in anything we serve: the feed, MCP and the terminal only return rows that passed verification.
verify_notesstringThe verification result; for served rows it is always "ok:source_record_present".
excludedbooleanAlways false in anything we serve (public bodies, schools and health care are excluded).
summarystringA one-sentence description built only from the fields above. Nothing in it is written by hand.
first_seen_attimestampUTC timestamp of the nightly pull that first saw this filing. Rows loaded when a state was first added, or by a backfill, carry their notice date at 12:00 UTC instead, so history is never announced as new. This is the field the morning digest uses to decide what is new; use it the same way.
last_seen_attimestampUTC timestamp of the most recent nightly pull in which the state still listed this filing.
signalstringyesThe signal the row belongs to: always "warn" for a row of filings (a WARN filing). The United Kingdom's insolvency and administration notices are a separate signal, "uk_gazette", served under the envelope's uk_notices list once the UK is live, never inside filings. Rows written before 2026-10-23 are served as "warn" too.

The public (masked) row

Without a token, each row carries exactly these eight fields; nothing that names or locates the employer.

FieldMeaning
filing_idAs in the subscriber view: the filing's stable 24-character key.
scoreAs in the subscriber view: 0-100 from the fixed rule on /methodology.
stateAs in the subscriber view: the two-letter code of the state that published the filing.
sectormanufacturing (NAICS 31-33 or manufacturing words in the industry text), logistics (NAICS 23, 48, 49 or transport, warehousing, trucking, logistics or construction words) or other.
workers_bandThe head count as a band, or "not stated" when the filing carries no usable count.
notice_dateAs in the subscriber view: the date the state records for the notice.
notice_typeAs in the subscriber view: closure versus layoff as the state words it; empty where the state publishes none (Texas).
signalAs in the subscriber view: The signal the row belongs to: always "warn" for a row of filings (a WARN filing). The United Kingdom's insolvency and administration notices are a separate signal, "uk_gazette", served under the envelope's uk_notices list once the UK is live, never inside filings.

Markets and signals

The feed, the MCP tools and the terminal serve the markets listed as live on /coverage; each row carries the signal of the rule that scored it. Live today: Texas, Wisconsin, Illinois, New York, Ohio and Florida.

MarketCodeSignalA row is
TexasTXwarna WARN notice as the state published it
WisconsinWIwarna WARN notice as the state published it
IllinoisILwarna WARN notice as the state published it
New YorkNYwarna WARN notice as the state published it
OhioOHwarna WARN notice as the state published it
FloridaFLwarna WARN notice as the state published it

markets: Optional. Comma-separated market codes, U.S. states and countries alike (the codes above; case does not matter). With it, filings carries the U.S. states named (none when no state is named; with states too, only the states named by both) and uk_notices is present only when UK is named and the United Kingdom is live; the envelope echoes the codes applied as markets. Codes that are not live are ignored. Without it the response is exactly what it was before the parameter existed: every covered state, and the UK list whenever the United Kingdom is live.

# several markets in one call
curl -s "https://firstvectorsignals.com/api/signals?token=$FV_TOKEN&days=45&markets=TX,IL,UK"

That call returns Texas and Illinois filings and, once the United Kingdom is live, its notices under uk_notices; until then the UK code is ignored. A code that is not live is ignored; asking only for such codes returns count 0.

Example responses

Public response, live

{
  "count": 5,
  "days": 14,
  "states": [
    "TX",
    "WI",
    "IL",
    "NY",
    "OH",
    "FL"
  ],
  "full": false,
  "access": "public",
  "filings": [
    {
      "score": 80,
      "state": "FL",
      "sector": "manufacturing",
      "workers_band": "150-499",
      "notice_date": "2026-10-14",
      "notice_type": "",
      "filing_id": "c55f94f2fff415f8d013463f",
      "signal": "warn"
    },
    {
      "score": 75,
      "state": "FL",
      "sector": "manufacturing",
      "workers_band": "50-149",
      "notice_date": "2026-10-15",
      "notice_type": "",
      "filing_id": "37dbf8f031d58ad0a7e19727",
      "signal": "warn"
    }
  ],
  "note": "Company names, cities and the state listing are shown to subscribers and trial members."
}

The live response for /api/signals?days=14 as this page loaded: 5 rows, the first 2 shown.

Subscriber response, live shape

{
  "count": "\u2026",
  "days": 45,
  "states": [
    "TX",
    "WI",
    "IL",
    "NY",
    "OH",
    "FL"
  ],
  "full": true,
  "access": "full",
  "filings": [
    {
      "filing_id": "c55f94f2fff415f8d013463f",
      "state": "FL",
      "company": "<text, withheld on this page>",
      "dba": "<text, withheld on this page>",
      "city": "<text, withheld on this page>",
      "county": "<text, withheld on this page>",
      "address": "<text, withheld on this page>",
      "workers": "<integer, withheld on this page>",
      "notice_date": "2026-10-14",
      "effective_date": "<date, withheld on this page>",
      "notice_type": "",
      "reason": "<text, withheld on this page>",
      "industry": "<text, withheld on this page>",
      "naics": "<text, withheld on this page>",
      "source": "FloridaCommerce WARN notices (Rapid Response System)",
      "source_url": "<text, withheld on this page>",
      "score": 80,
      "score_breakdown": "workers 150-499 (+35); notice type not stated (+0); manufacturing NAICS/industry (+20); filed within 14 days (+20); effective date on record (+5)",
      "verified": true,
      "verify_notes": "ok:source_record_present",
      "excluded": false,
      "summary": "<text, withheld on this page>",
      "first_seen_at": "2026-10-14T12:00:00+00:00",
      "last_seen_at": "2026-10-24T00:48:23.535407+00:00",
      "signal": "warn"
    }
  ]
}

Shape of the subscriber feed, with one live row as this page loaded. Values that identify the employer are replaced by their type here; your feed carries them.

MCP endpoint (firm license and regional charter)

https://firstvectorsignals.com/mcp?token=YOUR_TOKEN

A small Model Context Protocol server with read-only tools. It speaks JSON-RPC 2.0 over HTTP POST; each request gets its answer in the response body. Authorisation is the same token query parameter as the feed: a firm-license or charter token gets the full rows; a missing or unknown token gets the public window, exactly what the feed serves without a token (masked rows, 14 days, 25 rows, access "public" and a note; run_morning_job stays operator-only); an analyst-seat or trial token gets HTTP 401 ("token or admin key required").

  • POST /mcp?token=… with a JSON-RPC body. Methods: initialize (returns protocolVersion "2024-11-05", a tools capability and serverInfo name "firstvector-signals"), tools/list, tools/call, and the notifications/initialized notification (answered with HTTP 204). Any other method returns JSON-RPC error -32601.
  • GET /mcp?token=… opens a text/event-stream that sends a single endpoint event pointing back to /mcp and then closes. Tool results always come back in the POST response. There are no sessions, resources, prompts or server-initiated messages.
  • We test the endpoint with direct JSON-RPC calls like the ones below. We have not certified any particular desktop assistant or agent framework against it; tell us which client you use and we will test it with you.
Tool (from tools/list)Description
get_firstvector_statusService health, counts of verified filings and last ingest time.
list_warn_filingsVerified WARN filings ranked by score. Args: days (<=45), states (list), limit.
run_morning_jobADMIN: run the 06:00 digest/CEO pulse job now (dry_run supported).

get_firstvector_status returns filings_30d (verified filings with a notice date in the last 30 days), filings_24h (verified filings first seen since the last completed morning digest; the name is historical) and last_ingest (UTC date and time of the last successful nightly pull); if our database is unreachable these read "unavailable" rather than 0. list_warn_filings takes days (default 14, clamped 1–45), states (unknown codes ignored) and limit (default 50, clamped 1–300) and returns {"filings": [...]} with the subscriber feed's fields; a non-integer days or limit returns an error. run_morning_job is operator-only; with a subscriber token it returns {"error": "admin only"}. Results come back the MCP way: result.content[0].text is a JSON string you parse, and result.isError is true when the text is an error object.

# list the tools
curl -s -X POST "https://firstvectorsignals.com/mcp?token=$FV_TOKEN" -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

# Ohio and Illinois, last 30 days, top 20 by score
curl -s -X POST "https://firstvectorsignals.com/mcp?token=$FV_TOKEN" -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"list_warn_filings","arguments":{"days":30,"states":["OH","IL"],"limit":20}}}'

# response shape
{"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"{\"filings\": [ ... ]}"}],"isError":false}}

Keep the token out of version control; the URL contains your credential.

Watchlists

Every seat and trial has a watchlist in the terminal (the Watchlist link in the toolbar): paste the names you lend to, own or supply, one per line, each for one state or any state. A trial holds 5 names, an analyst seat 25; a firm license has no limit.

Every morning, right after the digest, the matcher compares the verified filings first seen since its previous pass with every name. Names are compared in a normalised form (lower case, punctuation dropped, Inc, LLC, Corp and the like removed) and scored 0 to 100 from the shape of the whole string and the words the two names share. At 80 or above the match is emailed to you in a separate message right after the morning digest; from 60 to 79 it is stored and shown in the terminal as "possible", never emailed; below 60 it is not a match. Every alert line and terminal row shows the name you watch, the name exactly as the state filed it, the confidence and the link to the state record. A match is a name similarity, never a confirmed identity: open the record before you act.

Matching looks forward: a filing first seen before you added a name is never emailed, so the watchlist page also shows a live check of your terminal's 45-day window against your names. The list and its match history export as CSV from the terminal toolbar. The feed and MCP do not carry watchlists.

Changelog

Contract version 2026-10-23. Versions are dates. Every change to the fields of either view, the envelope or the MCP tools and their arguments is dated here, newest first, and the same file carries an append-only list of every field ever published. The test suite renders both views against that file before every deploy, so a field that stopped being served would stop the deploy rather than reach you.

DateChange
2026-10-23First published version. Records what /api/signals and the MCP endpoint already served: the seven public fields, the 24 subscriber fields in table order, the response envelope and the three MCP tools with their arguments. Blank text fields are now always served as an empty string on every backend (null is reserved for a missing date), and the subscriber row always carries every field in this order. No field was added, renamed or removed.
2026-10-23Added the billing section: the currency a subscription is billed in (usd, the default, or gbp for UK customers; the founding seat usd only) and the VAT sentence (GBP prices exclude VAT for UK business customers; the reverse charge applies with a VAT number given at checkout). It documents the subscriber record, which the feed and the MCP tools never serve; no served field, tool or argument was added, renamed or removed, and the version is unchanged.
2026-10-23Track G stage UK. Added signal to both views of filings (always "warn": the eighth public field and the 25th subscriber field, last in table order) and the optional envelope list uk_notices, present only once the United Kingdom is live, carrying The Gazette's insolvency and administration notices as their own signal, uk_gazette, with the public and full row shapes documented in the new uk_notices section. Nothing was renamed or removed; the version date is unchanged because versions are dates and this is the same day.
2026-10-23Track G stage UK, the cross-border lens (append-only). Added the optional markets query parameter to GET /api/signals (comma-separated market codes: U.S. state codes and country codes such as UK) and the optional envelope key markets that echoes the codes applied only when the parameter was given; with the parameter, filings carries the U.S. states named and uk_notices is present only when UK is named and the United Kingdom is live. Without the parameter the response is unchanged. The market codes and the signal value per live market are listed on /developers#markets and /coverage#signals. Nothing was renamed or removed; the MCP tools are unchanged; the version date is unchanged because this is the same day.
2026-10-23Florida added as a new value of state (FL, read nightly from FloridaCommerce's Rapid Response System listing, live 2026-10-23). The field dictionary now says how Florida's columns map: its State Notification Date to notice_date, the start of its Layoff Date range to effective_date, its Industry sector name to industry, the lines under the company name to address and the city, and the year's listing page as source_url; notice_type stays empty because Florida publishes none. No field, tool or argument was added, renamed or removed; the version date is unchanged.

Deprecation policy

Fields are added, never renamed or removed without 30 days' written notice to firm-license holders. New fields may appear at any time, and new states appear as new values of the state field; existing names, types and meanings stay. If a field must ever be renamed or removed, every firm license and regional charter receives an emailed notice at least 30 days before the change, the schema version and this changelog record it, and the old name is served alongside the new one for those 30 days. Security fixes are the one exception and are explained in the changelog afterwards.

The notice goes to the address on your firm's account by email; tell us if a different address should receive it. Your own data, the 45-day window and your watchlist, can be exported as CSV from your terminal at any time.

Freshness, revisions and retention

When the data changes: once a night. Each state's file is fetched, normalised, verified and scored, then merged into our table, and the 6 a.m. Central digest goes out from the same data. If you poll once a day, do it after the 6 a.m. digest; polling more often returns the same rows.

What "new" means: a filing is new when its first_seen_at is later than your previous pull. That is exactly how the digest decides what to announce. notice_date is not a good signal of newness: states post anywhere from days to months after the employer's notice, so a filing can arrive with a notice date weeks old.

Revisions: states revise head counts in place. When they do, the same filing_id is updated (workers, score and last_seen_at change; first_seen_at does not). If a state rewrites a company name, notice date or site, it arrives under a new filing_id and the earlier row stays. A row the state has removed from its file keeps its last values; last_seen_at tells you the last night we saw it.

If a state's site is down overnight: that state is skipped and the others load as usual. Nothing is ever filled in or estimated, so a missing night means yesterday's rows for that state, not wrong rows. The last successful pull, overall and per state, is on the status page and returned by get_firstvector_status.

Window: the feed, MCP and the terminal serve filings with a notice date in the last 45 days. Older rows remain in our database but are not served; if you need a longer history, keep your own copy from your daily pulls or the terminal's CSV export (your license allows internal use of what you download, not redistribution).

Rates and uptime

Rate limits: none are enforced on the feed or MCP today. The public (no-token) response is cached for about a minute per server process; a subscriber request is served fresh from our database each time, with a 30-second query timeout. The service runs on a small autoscaling footprint (at most 10 instances, 20 concurrent requests each). If you expect more than a few hundred calls a day, email us first. We may add limits if a token is hammering the endpoint, and we will tell you before we do.

Uptime: we do not publish an SLA. The service runs on Google Cloud Run in us-central1 and scales to zero when idle, so the first request after a quiet period can take a few seconds while an instance starts. Deploys happen only after the test suite passes and are followed by an automatic check that /health and the home page respond. GET /health returns {"ok": true, "version": …, "commit": …, "time": …} and is the endpoint to watch. The status page shows the last successful pull per state, the last digest and inbox poll, and the incidents of the last two weeks; if something is wrong that it does not show, email us and a person answers.

Compatibility: we add fields and never rename or remove one without 30 days' emailed notice to firm-license holders (the deprecation policy), except for security fixes. New states appear as new values of state. The current contract version is 2026-10-23; the changelog lists every change.

Security and privacy

The web service is a container on Google Cloud Run (us-central1); filings and subscriber records live in Google BigQuery in the same Google Cloud project, in the United States. Nothing is hosted outside Google Cloud except Stripe, which handles payment. Secrets live in Google Secret Manager and are injected at deploy time; none are in the code repository, and deploys authenticate with short-lived federated credentials, not stored keys.

Your token is created when your seat or trial starts and is emailed to you once. Unsubscribe and feedback links in our emails never carry it; they use a one-way hash. Search engines are told not to crawl /api/ and /terminal/. For a call with your token we record the time of your last visit and a monthly count of pulls; we do not record which filings you look up. Cloud Run keeps ordinary request logs, which include the URL and therefore the token, for the operator's eyes only. The filings are public records; any personal contact detail a state prints on a notice stays in the raw record we keep for traceability and is never served by the feed, MCP, the terminal or the digest.

About the code

A tested codebase: 756 tests passed in the build that deployed this revision (the same count /health reports); every deploy is gated by the end-to-end self-test on a zero-traffic candidate revision, which takes traffic only after that test passes; verification fails closed: a filing without its state record is never verified, and only verified rows are served; API fields are append-only, with dated versions; secrets live in Google Secret Manager and never in the code or in logs; no customer data in the application's logs (the platform's request logs hold the URL, and so a token, as the Security section says). The code is not published; the evidence that it runs is the live answers at the top of this page.

Terms for the feed and endpoint, in short

Your token is a secret for your firm's own software: do not publish it or embed it in anything your clients can see. Email us if it leaks and we issue a new one. Use the data inside your firm's systems; do not resell, republish or build a competing dataset from it. There is no rate limit today and no uptime guarantee; we give 30 days' notice before changing the response format, except for security fixes. Full terms: /terms, sections 8 and 9.