Build on it
API reference
A versioned REST API over every record on the site — wells, permits, operators, production, Texas ownership — with provenance and freshness on every response.
Overview
Base URL: /api/v1 (same origin as this site) or https://terraledger.ayrical.com/api/v1 externally. The API is versioned by path segment (v1); breaking changes ship as v2 rather than mutating v1 in place.
Access & rate limits
Ordinary read requests work without credentials. Anonymous callers are limited by client address to 60 requests per minute. Send an optional x-api-key for a stable caller identity, a separately assigned limit, async export ownership, or a source-specific record family that requires an audit identity.
An invalid or revoked supplied key returns 401 unauthorized. A key that lacks a reviewed source-specific scope returns 403 forbidden_plan. Rate-limited API responses publish RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset. A 429 also carries Retry-After in seconds.
Requests accept at most 20 filter clauses, each no longer than 500 characters; q must be 3–200 characters and JSON request bodies are limited to 64 KiB.
Endpoints
Implemented v1 routes, grouped as they appear in the client. Detail routes take a numeric id except wells, which also accept the API/UWI.
Endpoint catalog
36 routes
| Method | Path | Description | Key params | Access |
|---|---|---|---|---|
| GET | /openapi.json | OpenAPI 3.1 discovery document for agents and generated clients. | — | Anonymous |
| GET | /health | Worker and database readiness, plus optional capability status. | — | Anonymous |
| GET | /coverage | Aggregate counts — wells, operators, states, periods covered. | — | Anonymous |
| GET | /sources | Registry of upstream agencies and systems the data is sourced from. | filter, sort, cursor, limit | Anonymous |
| GET | /freshness | Retrieval and publish timestamps per dataset. | — | Anonymous |
| GET | /schemas | Machine-readable field definitions for every record type. | — | Anonymous |
| GET | /search | Cross-entity text search across wells, operators, leases and properties. | q, limit | Anonymous |
| GET | /wells | Well search with quick search and the full filter grammar. | q, filter, sort, cursor, limit | Anonymous |
| GET | /wells/{id} | Well detail — header, nearby wells, permit and frac-disclosure counts. | — | Anonymous |
| GET | /operators | Operator directory. | q, filter, sort, cursor, limit | Anonymous |
| GET | /operators/{id} | Operator profile with production summary. | — | Anonymous |
| GET | /leases | Lease search. | q, filter, sort, cursor, limit | Anonymous |
| GET | /leases/{id} | Lease detail. | — | Anonymous |
| GET | /properties | Property search. | q, filter, sort, cursor, limit | Anonymous |
| GET | /properties/{id} | Property detail. | — | Anonymous |
| GET | /permits | Drilling permits. | q, filter, sort, cursor, limit | Anonymous |
| GET | /production | Production records at well/lease/property/operator/operator-county grain, with record_status. | filter, sort, cursor, limit | Anonymous |
| GET | /well-tests | Point-in-time well test rates. | filter, sort, cursor, limit | Anonymous |
| GET | /well-logs | Well log index — type, depths, external file URL. | filter, sort, cursor, limit | Anonymous |
| GET | /frac-disclosures | FracFocus hydraulic fracturing disclosures and chemicals. | filter, sort, cursor, limit | Anonymous |
| GET | /owners | Texas mineral/royalty owners from county appraisal districts. | q, filter, sort, cursor, limit | API key |
| GET | /mineral-interests | Texas mineral interests — decimal interest, appraisal value, legal description. | q, filter, sort, cursor, limit | API key |
| GET | /purchasers | Texas crude/gas purchasers. | q, filter, sort, cursor, limit | Anonymous |
| GET | /sales | Texas Comptroller severance-tax sales filings. | q, filter, sort, cursor, limit | Anonymous |
| GET | /map/wells | Individual well points inside a bounding box, for the map view. | bbox, filter, limit | Anonymous |
| GET | /map/clusters | Deterministic well clusters inside a bounding box at a zoom level. | bbox, zoom, filter | Anonymous |
| GET | /tiles/wells/{z}/{x}/{y}.mvt | Mapbox Vector Tile of wells for the given tile coordinate. | — | Anonymous |
| POST | /geo/search | Search wells around a point and radius. | body: lat, lon, radius_km, limit | Anonymous |
| GET | /analytics/daily-production | Normalized daily production rates with reporting grain preserved. | entity_type, id | Anonymous |
| GET | /analytics/boe | Current barrel-of-oil-equivalent conversion inputs and formula. | — | Anonymous |
| GET | /forecasts/{entity_type}/{id} | Decline-curve forecast for a well, lease, property or operator. | — | Anonymous |
| POST | /valuations | Compute a DCF or market-multiple valuation for an entity. | body: entity_type, entity_id, ownership_decimal, oil_price_usd, gas_price_usd, discount_rate | Anonymous |
| POST | /exports | Queue an async export job for a dataset with the current filter/sort/params. | body: dataset, format, params | API key |
| GET | /exports/{job_id} | Poll an export job's status; download completed artifacts through its download route. | — | API key |
| DELETE | /exports/{job_id} | Cancel a queued or running export job. | — | API key |
| GET | /exports/{job_id}/download | Download a completed export owned by the supplied API key. | — | API key |
Filter grammar
Every list endpoint accepts repeatable filter query params in the form filter=<field>:<op>:<value>. Multi-value operators (in, between) join their values with a pipe.
GET /v1/wells?filter=state_code:eq:TX&filter=cum_oil_bbl:gt:100000&filter=status:in:producing|shut_inOperators
12 ops
| Op | Meaning |
|---|---|
| eq | Exact match. |
| neq | Excludes exact match. |
| contains | Case-insensitive substring match. |
| ncontains | Excludes case-insensitive substring match. |
| lt | Less than. |
| lte | Less than or equal to. |
| gt | Greater than. |
| gte | Greater than or equal to. |
| in | Matches any of a list of values, pipe-joined. |
| between | Inclusive range — two pipe-joined values, low then high. |
| is_null | Field is null or missing. No value. |
| not_null | Field is present. No value. |
sort=field sorts ascending, sort=-field descending. Pagination is cursor-based: cursor is an opaque token from the previous response’s meta.next_cursor; limit caps the page size at 500 (default 50).
Response envelope
List endpoints return a data array plus a meta block carrying an exact or explicitly capped total, the cursor for the next page, and freshness.
List envelope
{
"data": [
{
"id": 48213,
"api_number": "4212330001",
"name": "SPRABERRY UNIT 12",
"operator_name": "PIONEER NATURAL RESOURCES",
"state_code": "TX",
"county": "Midland",
"status": "producing",
"cum_oil_bbl": 184220
}
],
"meta": {
"next_cursor": "eyJvIjo1MH0",
"total": 4821,
"total_capped": false,
"returned": 50,
"source_period": "2026-05",
"retrieved_at": "2026-06-02T14:05:00Z",
"published_at": "2026-06-03T09:00:00Z",
"as_of": "2026-08-05T18:22:11Z"
}
}Detail envelope
{
"data": {
"id": 48213,
"api_number": "4212330001",
"name": "SPRABERRY UNIT 12",
"status": "producing",
"nearby_wells": []
},
"meta": {
"source_period": "2026-05",
"retrieved_at": "2026-06-02T14:05:00Z",
"published_at": "2026-06-03T09:00:00Z",
"as_of": "2026-08-05T18:22:11Z"
}
}Error envelope
{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded"
}
}Error codes
7 codes
| Code | Meaning |
|---|---|
| unauthorized | An API key was required, invalid, or revoked. |
| forbidden_plan | The supplied key lacks a source-specific data scope. |
| not_found | No endpoint or record at this path. |
| invalid_request | Malformed filter, sort, or parameter. |
| rate_limited | Too many requests for this identity — see Retry-After. |
| internal | The API failed to answer. Retry shortly. |
| not_implemented | Specified, but unavailable in this deployment — vector tiles need PostGIS. |
Record status
Every production figure carries a record_status so a filing is never read as a calculation. The four statuses are never mingled into one silent series — a chart or export that spans them keeps them as separate, labeled series.
- reported
- Filed as-is by the source agency or operator — not derived.
- allocated
- Split from a lease- or unit-level report down to a well, by a documented weight.
- estimated
- Modeled where no report exists, with method, inputs and confidence stored.
- forecast
- Projected beyond the reported history by a decline-curve model, versioned.
curl examples
Filtered well search, sorted, limited
curl "https://terraledger.ayrical.com/api/v1/wells?q=wolfcamp&filter=state_code:eq:TX&filter=cum_oil_bbl:gt:100000&sort=-cum_oil_bbl&limit=50"Cursor follow-up (same filters, next page)
curl "https://terraledger.ayrical.com/api/v1/wells?q=wolfcamp&filter=state_code:eq:TX&filter=cum_oil_bbl:gt:100000&sort=-cum_oil_bbl&limit=50&cursor=eyJvIjo1MH0"Map clusters for a bounding box at a zoom level
curl "https://terraledger.ayrical.com/api/v1/map/clusters?bbox=-103.5,31.0,-101.0,33.0&zoom=7"Export lifecycle: queue, poll, download
# 1. Queue the export
curl -X POST -H "x-api-key: $TERRALEDGER_KEY" -H "Content-Type: application/json" \
-d '{"dataset":"wells","format":"csv","params":{"filter":["state_code:eq:TX"]}}' \
https://terraledger.ayrical.com/api/v1/exports
# 2. Poll until it leaves queued/running
curl -H "x-api-key: $TERRALEDGER_KEY" \
https://terraledger.ayrical.com/api/v1/exports/exp_9f2c1a
# 3. Download once status is "completed"
curl -H "x-api-key: $TERRALEDGER_KEY" -OJ \
https://terraledger.ayrical.com/api/v1/exports/exp_9f2c1a/downloadExport lifecycle
An export is a job, not a download. POST /exports queues it against a dataset and the same filter/sort/params a list request would use; GET /exports/{job_id} polls it; DELETE /exports/{job_id} cancels a queued or running job.
- Statuses
queued→running→completed|failed|expired. A completed job carriesrow_countand is fetched from the documented download route; a failed job carrieserror.- Formats
csv,ndjson,geojson. Export jobs require an API key so status, cancellation and download remain scoped to the caller that created them.- Expiry
- A completed job stays downloadable until its
expires_at; after that its status becomesexpiredand the export must be re-queued.