Skip to content

STF-1794: Add OpenAPI specs for the web services - #1

Merged
oschwald merged 31 commits into
mainfrom
greg/stf-1794-geoip-openapi-spec
Sep 25, 2026
Merged

oschwald merged 31 commits into
mainfrom
greg/stf-1794-geoip-openapi-spec

Conversation

@oschwald

@oschwald oschwald commented Sep 23, 2026 •

Copy link
Copy Markdown
Member

Part of STF-1794.

This adds OpenAPI 3.1 specs for the MaxMind public web services, and the tooling to lint, bundle, and publish them. Review it one commit at a time: there is one commit for each group of specs.

Specs

Spec Covers
geoip.yaml GeoIP Country, City Plus, and Insights, and GeoLite Country and City
minfraud.yaml minFraud Score, Insights, and Factors, Report Transaction, Dispositions, and the Alerts webhook
privacy-exclusions.yaml GET /privacy/exclusions
license-key-validation.yaml POST /secrets/validate-license-key
downloads.yaml Database and geofeed report downloads

Conventions

  • The specs describe only what the published developer documentation and the public client libraries already state. They add no implementation details.
  • Response schemas allow unknown keys, because new fields are not breaking changes.
  • Closed enums are used only for request inputs with a documented fixed list.
  • Examples are the published example bodies.

Tooling

  • Redocly lints the specs and checks each example against its schema.
  • CI checks that bundled/ matches the sources, and zizmor checks the workflows.
  • The Go package embeds the bundles, so Go tests can validate responses against them.
  • The tools are pinned with mise and pnpm, and run through precious.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added API specifications for GeoIP and GeoLite lookups, minFraud services, privacy exclusions, license key validation, and database and geofeed downloads.
    • Added sample requests and responses to illustrate common API interactions.
    • Go applications can access the bundled API specifications.
  • Documentation
    • Added project setup, compatibility, versioning, and usage guidance, plus a 0.1.0 changelog and Apache 2.0 license.
  • Chores
    • Added automated checks for linting, generated specifications, Go code, and workflow security.

@coderabbitai

coderabbitai Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Important

Review skipped

We couldn't safely recover the incremental review. No full review was started, and the last reviewed checkpoint was preserved. Retry later, or explicitly request a full review by commenting @coderabbitai full review.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Free

Run ID: 94215040-e163-4235-8a41-6791d74e9fad

📥 Commits

Reviewing files that changed from the base of the PR and between b010459 and 282de5b.

⛔ Files ignored due to path filters (2)
  • mise.lock is excluded by !**/*.lock
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (42)
  • .github/dependabot.yml
  • .github/workflows/ci.yml
  • .github/workflows/zizmor.yml
  • .gitignore
  • .precious.toml
  • .prettierrc.json
  • CHANGELOG.md
  • LICENSE
  • README.md
  • bundled/downloads.yaml
  • bundled/geoip.yaml
  • bundled/license-key-validation.yaml
  • bundled/minfraud.yaml
  • bundled/privacy-exclusions.yaml
  • components/errors.yaml
  • components/geoip-records.yaml
  • components/minfraud-request.yaml
  • components/minfraud-response.yaml
  • examples/geoip/city.yaml
  • examples/geoip/country.yaml
  • examples/geoip/insights.yaml
  • examples/minfraud/disposition-bad-request.yaml
  • examples/minfraud/disposition-updates.yaml
  • examples/minfraud/error.yaml
  • examples/minfraud/factors.yaml
  • examples/minfraud/insights.yaml
  • examples/minfraud/report-bad-request.yaml
  • examples/minfraud/request.yaml
  • examples/minfraud/score-bad-request.yaml
  • examples/minfraud/score.yaml
  • examples/minfraud/transaction-report.yaml
  • examples/privacy-exclusions/exclusions.yaml
  • go.mod
  • mise.toml
  • openapi.go
  • package.json
  • redocly.yaml
  • specs/downloads.yaml
  • specs/geoip.yaml
  • specs/license-key-validation.yaml
  • specs/minfraud.yaml
  • specs/privacy-exclusions.yaml

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The repository adds OpenAPI specifications and bundled documents for GeoIP, minFraud, downloads, license-key validation, and privacy exclusions. It also adds reusable schemas, examples, Go access to bundled YAML, and repository tooling for development, linting, and CI.

Changes

OpenAPI service catalog

Layer / File(s) Summary
Repository tooling and package access
.github/*, .gitignore, .precious.toml, .prettierrc.json, mise.toml, package.json, redocly.yaml, go.mod, openapi.go, README.md, CHANGELOG.md, LICENSE
Adds Dependabot and CI workflows, tool configuration, lint and bundle commands, repository documentation, and an embedded Go filesystem for bundled/*.yaml.
GeoIP lookup contracts
specs/geoip.yaml, components/geoip-records.yaml, examples/geoip/*, bundled/geoip.yaml
Adds GeoIP and GeoLite Country, City, and Insights operations, response schemas, example payloads, and the bundled specification.
minFraud contracts
specs/minfraud.yaml, components/minfraud-*, examples/minfraud/*, bundled/minfraud.yaml
Adds Score, Insights, Factors, transaction-report, disposition-update, and alert contracts, with request and response schemas, examples, and the bundled specification.
Database and geofeed downloads
specs/downloads.yaml, bundled/downloads.yaml
Adds authenticated GET and HEAD contracts for database and geofeed report downloads, with parameters, redirects, response headers, and error responses.
License validation and privacy exclusions
specs/license-key-validation.yaml, specs/privacy-exclusions.yaml, components/errors.yaml, examples/privacy-exclusions/*, bundled/license-key-validation.yaml, bundled/privacy-exclusions.yaml
Adds the license-key validation and privacy-exclusions API contracts, shared error schema, an exclusion response example, and bundled documents.

Estimated code review effort: 4 (Complex) | ~60 minutes


A rabbit hops through YAML rows
And checks each schema as it goes
Five service maps take shape in view
Bundled files join the toolkit too
The burrow’s build checks run on cue

Comment @coderabbitai help to get the list of available commands.

@linear-code

linear-code Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
STF-1794 MaxMind public web services have published OpenAPI specs

External users have no machine-readable description of the MaxMind public web services. This issue tracks OpenAPI 3.1 specs for those services in the new private repo https://github.com/maxmind/openapi, plus tests that check the specs against the implementation in mm_website.

Scope

  • GeoIP and GeoLite web services v2.1
  • minFraud Score, Insights, and Factors v2.0, Report Transaction, the Dispositions API, and the Alerts webhook
  • Privacy Exclusions API
  • License Key Validation API
  • Database and geofeed report downloads (spec only, with no implementation tests, because a Cloudflare Worker serves them)

The legacy text APIs, device tracking, and the browser JavaScript client are out of scope.

Plan

  1. Repo, shared components, the GeoIP spec, and CI (Redocly lint, example checks, and a bundle freshness check). In review: STF-1794: Add OpenAPI specs for the web services #1
  2. A validating HTTP transport in go/pkg/mmtesting/ that checks each response in the existing daemon integration tests against the spec. It also fails on undeclared fields. Wire it into rest_error_tester.go and the geoip2-daemon tests. Built on branch greg/stf-1794-openapi-response-validation. The PR waits until the openapi repo is public, because mm_website cannot fetch the private module.
  3. The minFraud spec, wired into the minFraud, Report Transaction, and Dispositions tests. Spec in review in STF-1794: Add OpenAPI specs for the web services #1
  4. The remaining specs, wired into the remaining daemon tests. Specs in review in STF-1794: Add OpenAPI specs for the web services #1
  5. Release v1.0.0 and make the repo public. The harness PR and its wiring into every daemon's tests move to ENG-5462.

Related fixes

Acceptance criteria

  • Each in-scope API has a spec that passes Redocly lint.
  • The daemon integration tests validate their responses against the published specs.
  • The repo is public, and dev-site links to it.

STF-1823 Audit and enforce public API request limits

The OpenAPI review identified input fields without documented or enforced
field-specific bounds. Report a Transaction's transaction_id and
chargeback_code have no field length check in the handler. A whole-request
limit does not establish the intended bound for each field. New constraints
must be established in the service before the OpenAPI schema promises them.

Scope

Audit request bodies, query parameters, path parameters, and headers consumed
by the five public API groups: GeoIP/GeoLite, minFraud (including Report and
Dispositions), Privacy Exclusions, License Key Validation, and database/geofeed
downloads. Include the Cloudflare worker and edge enforcement where relevant.

Cover string lengths, collection sizes, map keys, numeric ranges, and whole
requests. Include shopping-cart counts, custom-input collections, tracking
tokens, and license-key inputs in the inventory; absence of schema metadata
alone does not prove that a service bound is missing.

Acceptance criteria

  • Record each field's existing bound, units, enforcement point, documented
    contract, and OpenAPI representation. Recognize effective bounds supplied by
    validated formats and fixed enumerations.
  • Distinguish missing documentation/schema metadata from missing server
    enforcement. Record whole-request limits separately from field bounds.
  • Select compatible limits for confirmed gaps. A 255-character report
    transaction ID limit is a candidate because scoring uses that limit; assess
    compatibility before adopting it.
  • Enforce chosen limits at the service boundary and define the existing
    endpoint-appropriate error or warning behavior. Do not silently truncate.
  • Test boundary and over-limit values, Unicode characters versus bytes, empty
    and null inputs where applicable, and collection/map boundaries.
  • Implement independent gaps as separate fixes and commits. Update public
    documentation and OpenAPI once each service contract is established.

Related work

Review in Linear

@oschwald
oschwald force-pushed the greg/stf-1794-geoip-openapi-spec branch 9 times, most recently from 9c25c56 to 4338746 Compare September 23, 2026 20:36
Describe the GeoIP Country, City Plus, and Insights web services and
the GeoLite Country and City services in OpenAPI 3.1. The schemas,
descriptions, and examples follow the developer documentation.

CI lints the specs with Redocly, which also checks each example
against its schema, and checks that the committed bundle is current.
The Go package embeds the bundles so services can validate their
responses against them in tests.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@oschwald
oschwald force-pushed the greg/stf-1794-geoip-openapi-spec branch from 4338746 to 2f7f8e1 Compare September 23, 2026 21:10

@horgh horgh left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems good. I mostly skimmed it. I had Claude check it in a couple ways so there are a bunch of comments from it.

Comment thread specs/downloads.yaml Outdated
`<edition_id>_[<artifact_type>_]<YYYYMMDD>.<suffix>`.
schema:
type: string
Content-Type:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OpenAPI 3.1 says that a Content-Type entry under response headers SHALL be ignored. Thus this response does not describe the file media type. Tools that check media types can flag a HEAD response with Content-Type: application/gzip as undocumented.

Suggestion: remove this header entry. Declare content with the media types and no schema. The download worker sends application/gzip, application/zip, text/csv and, for the md5 and sha256 suffixes, text/plain. For example:

content:
  application/gzip: {}
  application/zip: {}
  text/csv: {}
  text/plain: {}

🤖 Comment by Claude (Claude Code) on behalf of Will.

@oschwald oschwald Sep 24, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the ignored Content-Type response-header entry. I kept HEAD bodyless rather than adding content, which describes response payload media types; the GET response already declares the download media types.

Commits: 5129765.

🤖 Response by Codex on behalf of Greg.

field that MaxMind does not recognize produces an `INPUT_UNKNOWN` warning.


A string field allows up to 255 valid Unicode characters unless its schema

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This text says that the 255-character limit applies "unless its schema states a shorter limit". But device.user_agent allows 512 characters and order.referrer_uri allows 1024. A client author who follows this text can truncate valid input.

Suggestion: change "shorter" to "different".

🤖 Comment by Claude (Claude Code) on behalf of Will.

@oschwald oschwald Sep 24, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed “shorter” to “different” in both repositories.

Commits: 5ae3d48, 3e70e821.

🤖 Response by Codex on behalf of Greg.

Comment thread specs/downloads.yaml
schema:
$ref: "../components/errors.yaml#/schemas/Error"
example:
code: ACCOUNT_ID_REQUIRED

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example pairs code: ACCOUNT_ID_REQUIRED with the message "An account ID and license key are required to use this service." A request with no credentials to the live geofeed endpoint returns this message with AUTHORIZATION_INVALID:

{"code":"AUTHORIZATION_INVALID","error":"An account ID and license key are required to use this service."}

The same mismatch is in specs/privacy-exclusions.yaml:176. A client coded from the example can branch on the wrong code for the no-credentials case. Suggestion: change the example code to AUTHORIZATION_INVALID.

🤖 Comment by Claude (Claude Code) on behalf of Will.

@oschwald oschwald Sep 24, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping this example: the service uses that same message for both AUTHORIZATION_INVALID and ACCOUNT_ID_REQUIRED. Missing credentials and a Basic header with an empty account ID take different paths (go/pkg/webservice/authutil.go and wserrors/error.go). This is an example of the missing-account response, not a promise that every request without credentials returns this code.

🤖 Response by Codex on behalf of Greg.

Comment thread examples/minfraud/insights.yaml Outdated
email:
domain:
classification: business
first_seen: "2015-01-20"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

first_seen: "2015-01-20" is earlier than the earliest possible date that EmailDomain.first_seen documents (2019-01-01). The same value is in examples/minfraud/factors.yaml:181.

Suggestion: use a date on or after 2019-01-01.

🤖 Comment by Claude (Claude Code) on behalf of Will.

@oschwald oschwald Sep 24, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected the example date in both repositories.

Commits: 8f96f9f, 6b48d56e.

🤖 Response by Codex on behalf of Greg.

disposition:
action: accept
reason: default
rule_label: my_custom_rule

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example pairs reason: default with rule_label: my_custom_rule. The schema says that rule_label is absent when no rule was triggered, and default means that no custom rule matched. The same pair is in examples/minfraud/insights.yaml:6 and examples/minfraud/factors.yaml:6.

Suggestion: remove rule_label, or change reason to custom_rule.

🤖 Comment by Claude (Claude Code) on behalf of Will.

@oschwald oschwald Sep 24, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aligned the reason with the custom rule label.

Commits: 8e99759, 415e7821.

🤖 Response by Codex on behalf of Greg.

phone_country_code:
type: string
pattern: "^[0-9]{1,4}$"
description: The international calling code for the phone number.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dev-site now says "Use digits only; do not include a leading +." for this field, and for credit_card.bank_phone_country_code at line 513. The pattern already enforces it, but the description should say it.

Suggestion: append that sentence to both descriptions.

🤖 Comment by Claude (Claude Code) on behalf of Will.

@oschwald oschwald Sep 24, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving this optional prose addition out of the current changes. Both schema patterns already require digits only and reject a leading +, so validators enforce the documented rule.

🤖 Response by Codex on behalf of Greg.

description: >-
The request body is too large. The response does not have a JSON body.
InternalServerError:
description: >-

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dev-site says "In both cases, you can try the request again later." for 500 and 503. The spec says "Send the request again later." only on the 503.

Suggestion: add the same sentence here.

🤖 Comment by Claude (Claude Code) on behalf of Will.

@oschwald oschwald Sep 24, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving the additional retry guidance out of this PR. The 500 response does not prohibit retrying; adding the sentence would be a documentation enhancement rather than a contract correction.

🤖 Response by Codex on behalf of Greg.

Comment thread components/minfraud-request.yaml Outdated
format: date-time
description: >-
The time the event occurred, in RFC 3339 format. If you omit this
field, MaxMind uses the time it receives the request. Do not send this

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This says "Do not send this field for a live transaction." Dev-site says "It is not recommended to use this input when scoring live transactions as they occur."

Suggestion: "MaxMind does not recommend this field for a live transaction."

🤖 Comment by Claude (Claude Code) on behalf of Will.

@oschwald oschwald Sep 24, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rephrased as a recommendation rather than a prohibition.

Commits: adf0dca.

🤖 Response by Codex on behalf of Greg.

Comment thread specs/geoip.yaml
value: 2001:db8::1:0:0:1
me:
value: me
Pretty:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dev-site curl examples send ?pretty with no value. This parameter has a string schema, so a generated client sends ?pretty=.

Suggestion: add allowEmptyValue: true.

🤖 Comment by Claude (Claude Code) on behalf of Will.

@oschwald oschwald Sep 24, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping this as-is: ?pretty= is accepted, so the generated-client form is valid. allowEmptyValue is not needed to force the bare ?pretty spelling, and callers do not need that exact spelling to enable pretty output.

🤖 Response by Codex on behalf of Greg.

Comment thread specs/minfraud.yaml Outdated
headers:
WWW-Authenticate:
description: >-
The authentication scheme, for example `Basic realm="minfraud"`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shared Unauthorized response is also used by the Dispositions endpoint at line 255. Dev-site says the Dispositions realm is minfraud-disposition, not minfraud.

Suggestion: give Dispositions its own Unauthorized response, or name both realms in this description.

🤖 Comment by Claude (Claude Code) on behalf of Will.

@oschwald oschwald Sep 24, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced the specific realm with a generic HTTP Basic challenge description. The shared response no longer claims that Dispositions uses the scoring service’s realm.

Commits: bfbdc68.

🤖 Response by Codex on behalf of Greg.

oschwald and others added 2 commits September 24, 2026 19:46
Describe the minFraud Score, Insights, and Factors endpoints, the
Report Transaction and Dispositions APIs, and the minFraud Alerts
webhook in OpenAPI 3.1. The schemas, descriptions, and examples follow
the developer documentation.

The Insights and Factors ip_address object reuses the GeoIP record
schemas instead of duplicating them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Describe the Privacy Exclusions API, the License Key Validation API,
and the database and geofeed report downloads in OpenAPI 3.1.

A successful download is a redirect, so Redocly's rule that requires
a 2xx response is off for the downloads spec only.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@oschwald
oschwald force-pushed the greg/stf-1794-geoip-openapi-spec branch from 2f7f8e1 to 4c64034 Compare September 24, 2026 19:46
@oschwald
oschwald merged commit dda10a2 into main Sep 25, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants