NewSee the ground in 3DLook

DrillerDB API conventions: errors, rate limits and versioning

How DrillerDB's APIs report errors, limit requests and change between versions: the public drillerdb.com endpoints and the Partner API.

DrillerDB API conventions: errors, rate limits and versioning

DrillerDB has two kinds of HTTP API:

  • Public endpoints on drillerdb.com, free and with no key: well depth and water-table lookup, the driller directory and license search, ZIP geocoding, platform stats and conference calendars. They are described in drillerdb.com/openapi.json.
  • The DrillerDB Partner API, for read-only access to your own company's data with an API key: https://console.drillerdb.com/api/partner/v1 and, on the classic app, https://app.drillerdb.com/api/v1.

Both follow the conventions below. The MCP server uses the Model Context Protocol and OAuth instead and is not covered here.

Errors

drillerdb.com public endpoints

Every error is JSON with an HTTP status that matches it. The data endpoints (well depth, license lookup, nearby drillers and ZIP geocoding) send two fields:

{"error":"invalid_zip","message":"Enter a 5-digit US ZIP code."}

error is a stable code you can branch on. message is written for a person.

The two conference calendar feeds send one field, error, and it holds a sentence, for example {"error":"Conference not found."}. Branch on the HTTP status for those.

Two errors apply to any /api path:

  • 404 when no endpoint exists at that path. The body adds hint and documentation:

    {"error":"not_found","message":"No API endpoint at /api/zzz.","hint":"The public endpoints are listed in https://drillerdb.com/openapi.json.","documentation":"https://drillerdb.com/openapi.json"}
    
  • 405 when the endpoint exists but does not accept that method. Every public endpoint is GET. The response carries an Allow header that lists the methods the endpoint accepts (GET, HEAD, OPTIONS; GET, HEAD on /api/stats) and the same four fields, with error set to method_not_allowed.

Partner API

Errors use an envelope with a request ID:

{"error":{"code":"API_KEY_REQUIRED","message":"This endpoint requires an X-API-Key header."},"meta":{"request_id":"req_d7b6ab54ad5b2532"},"links":{"self":"/api/partner/v1/projects"}}

Branch on error.code, for example API_KEY_REQUIRED (401), INSUFFICIENT_SCOPE (403) or RATE_LIMITED (429). On the classic app, error.hint says what to do next and links.documentation points at the OpenAPI document. Quote meta.request_id when you ask us about a specific call.

Rate limits

drillerdb.com

The limit is 500 requests per IP address per minute, counted across page views and API requests together. It is not a per-endpoint limit: loading pages from the same address uses the same quota.

Every /api response the limiter counted carries two header fields, as defined in draft-ietf-httpapi-ratelimit-headers (version 11):

RateLimit-Policy: "default";q=500;w=60
RateLimit: "default";r=482;t=37
  • q=500;w=60: the quota, 500 requests in a 60-second window.
  • r: requests you can still make now.
  • t: seconds until the oldest counted request leaves the window and quota starts to come back.

Over the limit, the response is a 429 with Retry-After (seconds) and this body:

{"error":"rate_limited","message":"Too many requests. Please try again shortly."}

Wait at least the Retry-After time before the next request. Ignore the RateLimit fields on a response that came from a cache (one with an Age header above 0), because the numbers are from when it was first sent.

Partner API

Each API key has a per-minute and a per-day limit. A new key starts at 120 requests per minute and 5,000 per day. Over either one, the response is a 429 with error.code RATE_LIMITED and a Retry-After header in seconds.

On the classic app (https://app.drillerdb.com/api/v1), a request that gets as far as the limit check also gets the two RateLimit fields back, with one entry for each of the key's windows. The limit check comes after the API key and its scope are accepted, so a 401 or 403 carries no fields. The windows follow the UTC clock: the minute window ends at the next full minute and the day window at midnight UTC.

RateLimit-Policy: "minute";q=120;w=60, "day";q=5000;w=86400
RateLimit: "minute";r=117;t=42, "day";r=4980;t=31000
  • q and w: the key's own limit and the window length in seconds.
  • r: requests the key can still make in that window.
  • t: seconds until that window ends.

A 429 carries the same fields beside Retry-After. A 500 error carries none, and neither does a key with no limits set. Browser clients on another origin can read the fields, because the API lists them in Access-Control-Expose-Headers.

The console Partner API (https://console.drillerdb.com/api/partner/v1) does not send RateLimit fields yet.

Versioning and deprecation

  • The version is in the path. The Partner API's major version is part of every URL: /api/partner/v1 on the console and /api/v1 on the classic app.
  • Additive changes stay in the current version. New response fields, new optional parameters and new endpoints can appear in v1 at any time. Write your client to ignore fields it does not know.
  • A breaking change gets a new major version path, such as /api/partner/v2. Removing or renaming a field, changing a type, or making an optional parameter required is a breaking change.
  • A deprecated version says so on every response. It sends a Deprecation header (RFC 9745), a Sunset header with the date it stops answering (RFC 8594), and a Link header with rel="deprecation" pointing at the migration notes.
  • At least 12 months pass between the date a version first sends the Deprecation header and its Sunset date.

No version is deprecated today.