@tthorstenson/juris
v0.1.0
Published
Juris MCP stdio server — UCC Article 9 debtor lien search with honest coverage.
Readme
Juris
Owned multi-niche structured-fact API for Trigg Thorstenson.
Working title: Juris
Phase A / first SKU: UCC Article 9 lien search by debtor name
Not this pass: thin alt-lending KYB pack on the same rails; Nursys; Apify/Amazon distribution.
This repository is first-party API + docs + MCP stub. Responses are labeled with honest provenance. Coverage admits holes.
What works today
| Surface | Status |
| --- | --- |
| POST /v1/ucc/search {debtor_name, states[]} | Yes. 1 credit. |
| GET /v1/coverage | Yes. 50 states + DC. Admits holes. |
| Stripe credit packs + API key auth | Yes. Test-mode $1 starter pack. |
| MCP search_ucc_liens + list_ucc_coverage | Yes (stdio stub over the HTTP API). |
| Day-1 adapters KY / RI / WI | Fixture-first, live interface present. |
| CO / MI | Catalogued as next (no adapter). |
| NY / TX / CA | HARD holes. No filings invented. |
Default search mode is fixture. Live SOS HTML is opt-in (JURIS_SEARCH_MODE=live or auto) and falls back to golden fixtures when parse fails.
Provenance contract
Every coverage row, search envelope, state result, and filing includes:
{
"source_url": "https://web.sos.ky.gov/ftucc/search.aspx",
"as_of": "2026-09-01T00:00:00.000Z",
"method": "fixture",
"confidence": 0.42
}method is fixture or live. Empty fixture rows mean no golden-record match, not “this debtor has no liens.” Juris is not a certified Secretary of State search.
Fixture vs live (probed 2026-09-05)
| State | Portal | Probe | Adapter |
| --- | --- | --- | --- |
| KY | https://web.sos.ky.gov/ftucc/search.aspx | Reachable. ASP.NET WebForms (tOrgname + bSearch). | Live POST implemented; result-table parse is conservative. Default fixture. |
| RI | https://business.sos.ri.gov/CorpWeb/UccSearch/UccSearch.aspx | Reachable HTTP 200. CorpWeb needs a radio postback before organization search. | Live GET/POST attempted; table parse not trusted. Default fixture. |
| WI | https://wims.dfi.wi.gov/uccsearch | Reachable HTTP 200. Angular SPA — no HTML form to POST. | Live = reachability probe only. Default fixture. |
| CO / MI | Official SOS pages catalogued | Not wrapped | next |
| NY / TX / CA | Official systems catalogued | Not wrapped | HARD hole |
Re-run: npm run probe.
Golden fixture debtor that returns filings in KY+RI+WI: ACME INDUSTRIES LLC.KY ONLY CORP hits KY only. Unknown names return kind=empty with fixture provenance.
ToS: GRAY
Automated access to KY FastTrack UCC, RI CorpWeb UCC, and WI WIMS is GRAY.
- Those sites are public search UIs, not licensed bulk APIs.
- KY itself says the HTML index is not a certified search.
- Live mode identifies as
Juris/0.1and does not bypass CAPTCHA, login, or paywalls. - Do not turn
JURIS_SEARCH_MODE=liveon against production SOS hosts without counsel + acceptable-use review. - Fixture mode does not hit SOS hosts.
No Nursys scrape. No third-party scraper marketplace.
Production $1 blockers
The local $1 path below is Stripe test mode. Taking a real dollar needs:
- Stripe live keys (
sk_live_…) and a public HTTPS webhook (STRIPE_WEBHOOK_SECRET). - A public
PUBLIC_BASE_URL(Checkout success + webhook). - Legal review of SOS ToS / acceptable use if you leave fixture mode (GRAY).
- Honest product copy: fixture data is demo; live HTML is not certified; NY/TX/CA are holes.
- WI has no HTML search surface — live WI needs an official API or a disclosed browser session (not in this pass).
- Name-matching is exact/normalized organization match on fixtures, not each state’s RA9 search logic.
- Some states will require accounts, fees, or certified-request workflows before a real search SKU.
Quick start
npm install
cp .env.example .env
npm test
npm run build
JURIS_DEV_ISSUE_KEY=true npm startDev-only key (never enable in production):
curl -s http://127.0.0.1:8787/v1/billing/dev-issue \
-H 'content-type: application/json' \
-d '{"credits":5}'Stripe test-mode $1 path
- Put a Stripe test secret key in
.envasSTRIPE_SECRET_KEY=sk_test_…(no surrounding quotes; whitespace is trimmed at load). - Start the API:
npm start(defaulthttp://127.0.0.1:8787). - Optional Render check (no Checkout):
GET /v1/billing/stripe-ping→{ok:true}or a mappedstripe_errorwith nesteddetail. - Create the $1 starter pack (1 search credit):
curl -s http://127.0.0.1:8787/v1/billing/checkout \
-H 'content-type: application/json' \
-d '{"pack":"starter"}'- Open
checkout_url. Pay with test card4242 4242 4242 4242, any future expiry, any CVC, any ZIP. - Stripe redirects to
/v1/billing/redeem?session_id=cs_test_…or sendcheckout.session.completedto/v1/webhooks/stripe(stripe listen --forward-to localhost:8787/v1/webhooks/stripe). - The redeem JSON includes
api_keyonce. Store it.
export JURIS_API_KEY='juris_sk_test_…'
curl -s http://127.0.0.1:8787/v1/account \
-H "authorization: Bearer $JURIS_API_KEY"
curl -s http://127.0.0.1:8787/v1/ucc/search \
-H "authorization: Bearer $JURIS_API_KEY" \
-H 'content-type: application/json' \
-d '{"debtor_name":"ACME INDUSTRIES LLC","states":["KY","RI","WI"]}'Packs: starter = $1.00 / 1 credit; pack5 = $5.00 / 6 credits.
Local webhook without a public URL: stripe listen --forward-to localhost:8787/v1/webhooks/stripe. Redeem still works if Checkout can reach PUBLIC_BASE_URL.
Coverage
curl -s http://127.0.0.1:8787/v1/coverageHole admission is the product. Requesting NY returns kind=hole, filings=[], confidence=0. Credits are still consumed (one search request, not one state).
MCP (stdio)
Official registry name: io.github.tthorstenson/juris
npm package: @tthorstenson/juris (stdio only; hosted /mcp is not registered)
Tools:
search_ucc_liens{ debtor_name, states? }list_ucc_coverage
Default search mode on the API is fixture. Live SOS HTML is ToS-GRAY and opt-in on the server (JURIS_SEARCH_MODE=live or auto).
Cursor / Claude Desktop
{
"mcpServers": {
"juris": {
"command": "npx",
"args": ["-y", "@tthorstenson/juris"],
"env": {
"JURIS_API_KEY": "juris_sk_…",
"JURIS_BASE_URL": "https://jurisapi.com"
}
}
}
}JURIS_API_KEY is required for search_ucc_liens. JURIS_BASE_URL is optional and defaults to https://jurisapi.com.
Local API
export JURIS_BASE_URL=http://127.0.0.1:8787
export JURIS_API_KEY=juris_sk_test_…
npm run mcpAPI
| Method | Path | Auth |
| --- | --- | --- |
| GET | / /docs /openapi.json | public |
| GET | /v1/coverage | public |
| GET | /v1/billing/packs | public |
| POST | /v1/billing/checkout | public |
| GET | /v1/billing/stripe-ping | public (Stripe connectivity; no Checkout) |
| GET | /v1/billing/redeem?session_id= | public (paid session) |
| POST | /v1/webhooks/stripe | Stripe signature when configured |
| GET | /v1/account | API key |
| POST | /v1/ucc/search | API key + 1 credit |
Credits live in SQLite (DATABASE_PATH, default ./data/juris.db). API keys are stored as SHA-256 hashes.
Layout
src/app.ts HTTP API
src/search-service.ts paid search
src/coverage-catalog.ts 50-state hole map
src/billing.ts Stripe packs + fulfillment
src/adapters/ KY / RI / WI + live HTTP
src/adapters/fixtures.ts golden records
src/mcp/ stdio MCP stubLater (not this pass)
- CO / MI adapters
- Alt-lending KYB pack on the same credit + provenance rails
- Official data licenses where ToS requires them
