Skip to main content

Muriel Python SDK

Muriel is the AI agent for U.S. drinking water data. It answers plain-English questions about any public water system from the monitoring results states publish, checks them against federal limits, and shows the records behind every answer.

There are three ways to use it, and this README covers them in the order most people need them:

  1. In your AI assistant: add the Muriel connector to Claude, ChatGPT, Cursor, VS Code or any MCP client and ask in the chat you already use. No code, no key.
  2. The agent, from code: muriel.query("...") sends a question to the same agent and returns the answer, the records it used, and any CSV it produced, shaped the way your program needs.
  3. The REST API: exact, parameter-driven queries for one system's history, one contaminant nationwide, every system in a state, or the system serving an address. Use this package's functions or call it over HTTP from any language.

All three draw on the same data and the same Muriel account and credits.

What Muriel covers

  • 88 million+ results from 32 states and 63,000+ water systems
  • Current: many states update daily, often within days of sampling
  • Deep: some states go back to the 1970s
  • Every contaminant states test for: lead and copper, PFAS, nitrate, disinfection byproducts, arsenic, coliform, VOCs and more

Every answer is built from the sample results themselves, not from summaries or annual reports, and says how current each state's data is. A gap is never passed off as a clean record. See exactly which states, contaminants and years are covered, live, at murielai.io/overview.

Muriel is built for people who work with drinking water: utilities and the regulators who oversee them, and the labs, engineering firms and vendors who serve them.


1. Use Muriel in your AI assistant (MCP connector)

The quickest way to put Muriel to work. Add one URL to your assistant, sign in to your Muriel account and click Allow. There is no API key to copy and nothing to install.

Connector URL: https://mcp.murielai.io/mcp

Assistant How to add it
Claude (claude.ai and desktop) Settings → Connectors → Add custom connector → name it Muriel, paste the URL → Connect
Claude Code claude mcp add --transport http --scope user muriel https://mcp.murielai.io/mcp, then /mcp → muriel → Authenticate
Cursor In ~/.cursor/mcp.json: {"mcpServers": {"muriel": {"url": "https://mcp.murielai.io/mcp"}}}
VS Code (Copilot agent mode) In .vscode/mcp.json: {"servers": {"muriel": {"type": "http", "url": "https://mcp.murielai.io/mcp"}}}
ChatGPT Where custom connectors are enabled: Settings → Connectors → create one with the URL and OAuth
Anything else Any client that supports remote MCP servers with OAuth takes the URL. For local-only (stdio) clients: npx -y mcp-remote https://mcp.murielai.io/mcp

The first time your assistant uses Muriel it opens a Muriel page: sign in if you need to, see what it is asking for, and click Allow. To disconnect, use Connected apps on your account page.

Then just ask, in the chat you already use:

Anything new with Chicago's water this week?

Which systems in Georgia had a nitrate result above the MCL in the last year?

Where has PFOA or PFOS been detected above 4 ppt in Pennsylvania?

Your assistant calls Muriel whenever a question needs drinking water data and answers from what Muriel returns. The connector gives it 16 read-only tools, the same ones Muriel's own agent uses:

Tools What they answer
find_water_system A utility or town name → its water system ID (PWSID)
get_sample_results, summarize_sample_results, deep_history_search Individual results and summaries, current and archived
get_notable_events, get_record_changes What changed in the latest updates
analyte_trend Whether a contaminant is going up or down
check_compliance_from_data, check_monitoring_gaps Running annual averages, 90th percentiles and missing samples
rank_systems_by_size State-wide questions with no system named
data_coverage, historical_depth What data exists for a state or system, and how far back
lookup_standard, evaluate_result, resolve_analyte, search_regulations The federal standards and rules

The connector can't see your Muriel conversations or documents, change your account or buy anything. Full reference: murielai.io/developers.


2. Ask the agent from code

Install the package and set your key. Create a key on your account page.

pip install muriel
export MURIEL_API_KEY="muriel_live_..."

Every function reads MURIEL_API_KEY; you can also pass api_key= directly.

import muriel

answer = muriel.query("Is Chicago's water meeting federal limits for lead and copper?")

print(answer.summary)                 # the answer, in plain English
for r in answer.results[:5]:          # the records it rests on
    print(r["sample_collection_date"], r["system_name"],
          r["result_analyte_name"], r["result_value"], r["result_unit"])

muriel.query() asks the same agent that answers at murielai.io. It works out which systems, contaminants and dates the question is about, pulls the results, compares them with MCLs, action levels and treatment rules, and tells you when a state's data is out of date. Each answer carries:

Field What it holds
summary The answer in plain English
results, count The records the answer rests on
documents Any CSV or PDF the agent produced, with a download URL
warning A caveat, when the data needs one
execution_log The steps the agent took
credits What the question used, and your balance after it

What you can ask

Compliance Which systems in Georgia had a nitrate result above the MCL in the last year?
Trends Which systems are within 20% of an MCL and trending up?
PFAS Where has PFOA or PFOS been detected above 4 ppt in Pennsylvania?
What changed What new results were published in California this week?
Market research Which Illinois systems serving over 50,000 people have detected PFAS?

Scope it

answer = muriel.query("Any new detections?", pwsid="IL0316000", since="2026-01-01")

Get exactly the fields you need

Pass a list of field names, a JSON schema or a Pydantic model, and the answer comes back in that shape, ready for a database, a dashboard or another program:

latest = muriel.query(
    "What was the most recent lead result for Chicago?",
    pwsid="IL0316000",
    schema=["sample_date", "result", "units", "above_action_level"],
)
from typing import List
from pydantic import BaseModel
from muriel import Agent

class SystemSummary(BaseModel):
    pwsid: str
    system_name: str
    total_samples: int
    contaminants_found: List[str]

summary = Agent().query("Summarize water quality for IL0316000.", schema=SystemSummary)
print(summary.system_name, summary.total_samples)

From question to CSV

Ask for an export and the file comes back with the answer:

answer = muriel.query("Which systems in Oregon have a lead 90th percentile above 10 ppb? "
                      "Export it to a CSV, largest systems first.")
for doc in answer.documents:
    print(doc["filename"], doc["url"])

3. Query the REST API directly

When you know exactly what you want, such as one system's full history or one contaminant across the country, query the data directly. Same key as above.

from muriel import get_results, get_lead, get_systems

# One water system, history included
rows = get_results(pwsid="IL0316000", analyte="Lead", limit=100)

# A whole state, or one group of contaminants
rows = get_results(state="IL", analyte_group="pfas", limit=500)

# The water system that serves an address, then systems nearby
rows = get_results(address="121 N LaSalle St, Chicago, IL", radius_miles=10)

# The latest lead results nationwide
rows = get_lead(limit=50, detections_only=True)

# Every water system in a state, with size and latest sample
systems = get_systems(state="IL")

The same calls work from any language over HTTP:

curl "https://api.murielai.io/api/v1/records/system/IL0316000?result_analyte_name=Lead&limit=12" \
  -H "X-API-Key: $MURIEL_API_KEY"

Every record has the same fields whichever state it came from: the system, the contaminant, the value as published and converted to mg/L, the sample date, the place where known, and era: "recent" for the window states currently publish, "historical" for the archive. Pass era="all" to include history in the national feeds, or era="recent" to limit a system or state query to current results.

Function What it returns
get_results() Results by system, state, analyte group or address, with filters for dates, detections, concentration and analyte
get_latest(), get_lead(), get_pfas(), get_coliform(), get_nitrate(), get_dbps(), get_arsenic(), get_manganese(), get_vocs() The latest results for one contaminant, nationwide
get_systems() Water systems in a state
get_state_sampling_points(), get_sampling_point_geocoding() Sampling points with a location
get_state_facilities(), get_facility_geocoding() Facilities with a location, where states publish them
SamplesClient, SystemsClient The same calls as objects

Up to 1,000 records per call; page with offset. Monitoring requirements and violation records (get_requirements, get_violations) are not available yet; ask the agent about monitoring gaps and exceedances instead.

Interactive reference for every route and field: api.murielai.io/docs.


Pricing

One balance of credits on your Muriel account covers all three:

  • An agent question, in the app or from code, is metered on the work it takes: typically 1–3 credits, never more than 8.
  • Data calls, through the connector's tools or the REST API, cost 1 credit per 1,000 records returned, added up across calls. A call that returns nothing is free.

Every account gets 8 free credits a month, usable on any of them. For more, email info@murielai.io.

Errors

from muriel import MurielAPIError, MurielCreditsError, get_results

try:
    rows = get_results(pwsid="IL0316000")
except MurielCreditsError:
    print("Out of credits.")
except MurielAPIError as e:
    print(e.status_code, e.message)
Exception When
MurielAuthenticationError Missing or invalid API key (401)
MurielValidationError A bad parameter (400)
MurielCreditsError Out of credits (402); nothing was charged
MurielNotFoundError Unknown system or resource (404)
MurielRateLimitError Too many requests (429); wait and retry
MurielServerError Not available (501), or a server error worth retrying (5xx)

All of them inherit from MurielAPIError. Keys are only ever sent over HTTPS, and never forwarded on a redirect.

Good to know

Muriel is a research and analysis tool, not a compliance determination. Results are as states published them; coverage varies by state, and the absence of a result is never evidence of compliance. Only your state primacy agency can say whether a system is in compliance.

Documentation

Support

License

MIT. Copyright (c) 2024-2026 Muriel AI, Inc. Use of Muriel is governed by the terms at murielai.io/terms.

Metadata

Release files for muriel 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for muriel 0.2.0
File Size Uploaded
muriel-0.2.0.tar.gz 29.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for muriel 0.2.0
File Interpreter ABI Platform
muriel-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 56.8 kB

Release files / muriel-0.2.0.tar.gz

Download URL muriel-0.2.0.tar.gz
Size 29.7 kB
Tags Source
SHA-256 checksum
How to use checksums
087f523bc9a5234373b87d4e4cd57bec1cc6d8ec8e7933e8c0d1dfdb80d3182e
BLAKE2b-256 checksum
How to use checksums
704f4a6ba4aabfcb1d1ae2e23fe82dfadf052b2a0c73626311efe75be7b23d10
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.

Transparency log

Release files / muriel-0.2.0-py3-none-any.whl

Download URL muriel-0.2.0-py3-none-any.whl
Size 27.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7c94956a7aeeb4b9d2d924f03bbd975b4def40bd36ae59c50a1ebc5896db55ae
BLAKE2b-256 checksum
How to use checksums
d576a4cc7eb748126baa27661e116f23fbd37f18345de64a82447098a329f219
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.

Transparency log

Release history Release notifications | RSS feed

0.2.7

2 release files

0.2.1

2 release files

This release

0.2.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page