Recipes and tools

Using the EZRRC API and Python SDK

Query any RRC dataset over HTTP, resolve code values, request an export in any format, and assemble a cross-dataset view of a single well — with curl, with the Python SDK, and from an MCP-capable assistant.

Everything on this site is also available programmatically. The API serves the same rows the download funnel serves, from the same warehouse, with the same code lookups attached. If your work is repeatable, use the API and skip the browser.

The OpenAPI 3.1 document is the authoritative reference and is published at /api/schema/, with browsable documentation at /api/docs/. This article is the orientation.

Authentication

Browsing the catalogue is anonymous. Reading rows and requesting exports needs an API key, which you create in your account settings. Keys look like ezrrc_pk_... and are shown once, at creation, because only a hash is stored.

Pass the key as a bearer token:

curl -H "Authorization: Bearer $EZRRC_API_KEY" \
     "https://ezrrc.com/api/v1/datasets/"

Rate limits are per key and generous for ordinary use. Anonymous requests get a per-minute burst allowance; keyed requests get a daily quota. When you exceed a limit you get HTTP 429 with a Retry-After header — respect it rather than retrying immediately, and prefer one large export to ten thousand small row requests.

Finding a dataset

curl -H "Authorization: Bearer $EZRRC_API_KEY" \
     "https://ezrrc.com/api/v1/datasets/?search=wellbore"

The list endpoint returns each dataset's slug, title, category, tier, source format, update cadence, whether it is spatial, and whether it is queryable. That last flag matters: a dataset in the warehouse tier has been parsed into queryable rows, while a mirror-tier dataset is available only as its pristine original file. You can download either; you can only query the first.

The detail endpoint returns the full metadata:

curl -H "Authorization: Bearer $EZRRC_API_KEY" \
     "https://ezrrc.com/api/v1/datasets/full-wellbore/"

That payload includes the complete field list — every column with its position, width, implied decimals, data type, description and, where one applies, the code set it resolves through — plus the relationships to other datasets with their join columns and cardinality. It is the machine-readable version of what this whole learning series describes.

Querying rows

curl -G -H "Authorization: Bearer $EZRRC_API_KEY" \
     "https://ezrrc.com/api/v1/datasets/drilling-permits-daily/rows/" \
     --data-urlencode "district=08" \
     --data-urlencode "issue_date__gte=2026-01-01" \
     --data-urlencode "select=permit_no,lease_name,issue_date,total_depth" \
     --data-urlencode "order_by=-issue_date" \
     --data-urlencode "limit=200"

Filters use column for equality and column__op for everything else. The operators are the ones the warehouse layer supports: eq, ne, gt, gte, lt, lte, like, in, isnull, notnull. Column names are validated against the live table before they reach SQL — an unknown column is a 400, never a query.

Responses are cursor-paginated. Follow next until it is null rather than computing offsets yourself; on tables of this size, cursor paging is the difference between a fast query and a timeout.

import requests

session = requests.Session()
session.headers["Authorization"] = f"Bearer {API_KEY}"

url = "https://ezrrc.com/api/v1/datasets/drilling-permits-daily/rows/"
params = {"district": "08", "issue_date__gte": "2026-01-01", "limit": 500}

rows = []
while url:
    payload = session.get(url, params=params).json()
    rows.extend(payload["results"])
    url, params = payload.get("next"), None    # the cursor carries the params

Resolving codes

Every coded column points at a code set, and code sets are addressable:

curl -H "Authorization: Bearer $EZRRC_API_KEY" \
     "https://ezrrc.com/api/v1/codes/rrc.application_type/"

You get code, label, description and, where one exists, a cross-dataset canonical value. This is how you turn 12 into DRILL-HORIZONTAL without hard-coding a dictionary that goes stale. Row responses can carry resolved labels alongside raw codes on request, and exports have the same option.

Requesting an export

Row queries are for slices. When you want a whole dataset, or a large filtered subset, or a format that is not JSON, request an export. It runs as a background job and lands as a downloadable artifact.

curl -X POST -H "Authorization: Bearer $EZRRC_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{
           "dataset": "full-wellbore",
           "format": "gpkg",
           "filters": {"field_district": "08"}
         }' \
     "https://ezrrc.com/api/v1/exports/"

The response carries an export id and a status. Poll it:

curl -H "Authorization: Bearer $EZRRC_API_KEY" \
     "https://ezrrc.com/api/v1/exports/42/"

Statuses run pending, running, then ready or failed. A ready export carries a time-limited download URL.

Two behaviours worth knowing. Exports are content-addressed and cached: the same dataset, same source version, same format and same filters returns the existing artifact instead of regenerating it, so re-requesting is cheap. And exports are bounded — there are row and byte ceilings, and a request that exceeds them fails with an explicit "narrow your filter" message rather than running for an hour and dying. If you genuinely need the whole of the largest production table, take the pristine original instead.

Tabular formats are CSV, TSV, JSON, NDJSON, XLSX and Parquet. Spatial datasets additionally offer GeoPackage, GeoJSON, KML, KMZ, shapefile and FlatGeobuf. Every dataset that has one also offers original — the exact bytes the RRC published.

The assembled well view

One endpoint does the cross-dataset assembly described in the joining article for you:

curl -H "Authorization: Bearer $EZRRC_API_KEY" \
     "https://ezrrc.com/api/v1/wells/42-329-31234/"

It returns the wellbore root, its location, its completions with their oil and gas schedule identities, casing and perforation summaries, formations, the permits that produced it, the operator, and the leases it rolls up into. It is the "everything about this hole" call, and it is the fastest way to sanity-check a join you have written yourself.

The Python SDK

The ezrrc package wraps all of the above with pagination, retries and type hints, and returns pandas or Arrow when you want them.

from ezrrc import Client

client = Client(api_key="ezrrc_pk_...")

# Metadata
dataset = client.dataset("full-wellbore")
print(dataset.title, dataset.update_cadence)
for field in dataset.fields():
    print(f"{field.name:24s} {field.data_type:10s} {field.description}")

# Rows, straight into a DataFrame
permits = (
    client.dataset("drilling-permits-daily")
          .rows(county="MIDLAND", since="2026-01-01")
          .to_pandas()
)

# A whole district as a GeoPackage
job = client.export("full-wellbore", format="geopackage", district="08")
path = job.wait().download("wellbore_d08.gpkg")
print(f"wrote {path}")

# Codes
for code in client.codes("rrc.application_type"):
    print(code.code, code.label)

job.wait() polls with backoff and raises on failure rather than returning a half-finished job. .download() streams to disk; it never buffers a multi-gigabyte file in memory.

Install it with pip install ezrrc. It has no mandatory heavy dependencies — pandas and pyarrow are optional extras, so the SDK is usable in a lambda-sized environment.

From an assistant

There is also an MCP server, so a model-driven assistant can use this data directly. It exposes read-only tools — search datasets, describe a dataset, query rows, look up a code, get a well — plus one side-effecting tool to request an export, which is separately rate-limited. It runs over stdio or streamable HTTP and ships with a Claude Desktop configuration snippet.

The practical value is that the assistant can consult the schema, the code sets and the relationship graph before it writes a query, rather than guessing column names — which, as everything in this series has hopefully demonstrated, is not a domain where guessing goes well.

Etiquette

The data is free and the service is ad-supported. A few requests:

  • Cache what you pull. Most of these datasets change monthly.
  • Use exports for bulk and row queries for slices, not the other way around.
  • Send a real User-Agent so problems can be traced back to you.
  • Back off on 429 and 5xx.
  • For anything at genuine scale, take the pristine originals and process locally. That is why they are published.

And the standing caveat from every page on this site: EZRRC is an independent mirror of public-domain data. For any legal or regulatory purpose, verify against the official Railroad Commission release.