API Tutorials

OpenAPI Contract Tests for Infrastructure APIs in 2026

DNS, WHOIS, IP, SSL, HTTP, and port checks often become release gates, monitors, client reports, and incident evidence. Those workflows need a second guardrail: tests that prove the API contract still matches the JSON your automation expects.

June 18, 20268 min readPlatform Engineering Team

The Failure Mode Most Teams Miss

Infrastructure automation usually fails in one of two ways. The outside world changes, or the parser breaks. The first problem is expected: a DNS record drifts, a certificate approaches expiration, an IP moves to a new ASN, a security header disappears, or a port is exposed. The second problem is quieter. The API still returns a response, but a field moves, an optional field is missing, a type changes, or an error body is not handled.

OpenAPI contract tests catch that second class of failure. They do not replace a DNS and SSL release gate, an exposure monitor, or an incident runbook. They protect those workflows by validating the contract they parse before the job becomes business-critical.

Ops.Tools publishes customer-facing API documentation and a curated OpenAPI JSON document. The public docs verify endpoint families for DNS lookup, WHOIS data, IP lookup, SSL checks, HTTP header analysis, port scanning, API keys, and usage reporting. The pricing page also lists an auto-generated TypeScript SDK and a Postman collection, which are useful signals for teams that want repeatable tests rather than hand-copied examples.

What to Test

Contract tests should stay small and boring. Do not turn them into a full monitoring suite. Pick the fields your workflow actually uses, then assert that the response still carries those fields with the documented types. For Ops.Tools, the public spec documents these stable surfaces:

Endpoint familyDocumented pathContract fields worth checking
DNS/v1-dns-lookupsuccess, address, type, records
WHOIS/v1-whois-datadomain, whoisText, optional whoisJson
IP Lookup/v1-get-ip-detailsip, countryCode, asn, organization
SSL/v1-ssl-checkercertificate.isValid, daysRemaining, chain
HTTP Analysis/v1-analyze-httpstatusCode, rawHeaders, analysis.security
Port Scanner/v1-port-scannertarget, ports, state, service
Usage/v1-get-api-usagedate, service, count, userId

Step-by-Step Workflow

  1. Pull the current OpenAPI document. Use the public JSON route your application depends on, and pin a copy in your repo when you need reviewable diffs.
  2. Select approved targets. Use domains, URLs, and IP addresses your organization owns or is authorized to test. Keep port checks especially narrow.
  3. Choose contract assertions. Assert required fields, types, status codes, and content type. Leave operational thresholds, such as minimum certificate days, to the release gate or monitor.
  4. Run a pull-request smoke set. Call low-risk passive checks such as DNS, WHOIS, SSL, IP, and usage shape tests. Run active or high-volume checks on a schedule.
  5. Fail with useful evidence. Store endpoint, status, field path, expected type, observed type, and a redacted response sample. Do not print API keys.
  6. Route drift separately from infrastructure findings. A contract failure belongs to the automation owner. A DNS or certificate failure belongs to the asset owner.

API Examples

These examples validate documented response shape. They are not benchmark claims, and they do not require a native CI, SIEM, or ticketing integration. They can run from GitHub Actions, GitLab CI, Buildkite, a cron job, or an internal worker.

Contract Drift Examples Worth Catching

Contract drift is not always dramatic. A DNS check can still return HTTP 200 while records is missing, changed from an array to a string, or returned only inside a nested provider object your parser does not read. A WHOIS job can still produce raw text while the optional parsed object disappears for a provider or TLD. An SSL check can still complete while the certificate object no longer exposes daysRemainingas a number. Each change is small until a dashboard silently marks a risky domain as healthy.

HTTP and port checks need extra care because teams often use them for security evidence. IfrawHeaders, analysis.security, port state, or service names change shape, the downstream result may be worse than a failed job: it may be a clean-looking report built from missing evidence. Contract tests should fail loudly when a field your workflow depends on stops matching the documented type.

How to Scope Tests Without Burning Credits

Treat contract tests as a small, budgeted workload. The Ops.Tools pricing page states that one credit equals one API request, so every extra endpoint call has a cost. A pull-request smoke suite should prove the parser can still read the contract, not audit an entire portfolio. One known domain, one known IP, one approved URL, and one tightly scoped port set are usually enough for fast feedback.

Larger coverage belongs in scheduled jobs. Run broader checks daily or weekly against a controlled manifest of production domains, customer domains, certificate surfaces, and investigation targets. Use the usage endpoint to review request counts by service, then adjust cadence before the contract suite becomes an invisible spend driver. This is especially important when the same API key supports release validation, domain portfolio monitoring, client reporting, and incident collection.

cURL: smoke-check documented fields

API_KEY="$OPS_TOOLS_API_KEY"
BASE="https://api.ops.tools"
DOMAIN="example.com"

DNS_JSON=$(curl -sG "$BASE/v1-dns-lookup" \
  -H "x-api-key: $API_KEY" \
  --data-urlencode "address=$DOMAIN" \
  --data-urlencode "type=MX")

echo "$DNS_JSON" | jq -e '
  .success == true and
  .address == env.DOMAIN and
  .type == "MX" and
  (.records == null or (.records | type == "array"))
'

SSL_JSON=$(curl -sG "$BASE/v1-ssl-checker" \
  -H "x-api-key: $API_KEY" \
  --data-urlencode "domain=$DOMAIN")

echo "$SSL_JSON" | jq -e '
  (.certificate.isValid | type == "boolean") and
  (.certificate.daysRemaining | type == "number")
'

TypeScript: a minimal contract runner

type CheckResult = { name: string; ok: true } | { name: string; ok: false; issue: string };

const baseUrl = 'https://api.ops.tools';
const headers = { 'x-api-key': process.env.OPS_TOOLS_API_KEY ?? '' };

function hasString(value: unknown): value is string {
  return typeof value === 'string' && value.length > 0;
}

function hasNumber(value: unknown): value is number {
  return typeof value === 'number' && Number.isFinite(value);
}

async function requestJson(path: string, params: Record<string, string>) {
  const url = new URL(path, baseUrl);
  for (const [key, value] of Object.entries(params)) url.searchParams.set(key, value);
  const response = await fetch(url, { headers });
  const json = (await response.json()) as Record<string, unknown>;
  return { response, json };
}

async function checkDnsContract(domain: string): Promise<CheckResult> {
  const { response, json } = await requestJson('/v1-dns-lookup', { address: domain, type: 'A' });
  if (!response.ok) return { name: 'dns', ok: false, issue: `HTTP ${response.status}` };
  if (json.success !== true) return { name: 'dns', ok: false, issue: 'success was not true' };
  if (!hasString(json.address)) return { name: 'dns', ok: false, issue: 'address missing' };
  if (json.type !== 'A') return { name: 'dns', ok: false, issue: 'type mismatch' };
  if (json.records != null && !Array.isArray(json.records)) {
    return { name: 'dns', ok: false, issue: 'records was not an array or null' };
  }
  return { name: 'dns', ok: true };
}

async function checkSslContract(domain: string): Promise<CheckResult> {
  const { response, json } = await requestJson('/v1-ssl-checker', { domain });
  const certificate = json.certificate as Record<string, unknown> | undefined;
  if (!response.ok) return { name: 'ssl', ok: false, issue: `HTTP ${response.status}` };
  if (!certificate) return { name: 'ssl', ok: false, issue: 'certificate missing' };
  if (typeof certificate.isValid !== 'boolean') return { name: 'ssl', ok: false, issue: 'isValid type mismatch' };
  if (!hasNumber(certificate.daysRemaining)) return { name: 'ssl', ok: false, issue: 'daysRemaining missing' };
  return { name: 'ssl', ok: true };
}

async function main() {
  const domain = process.env.DOMAIN_UNDER_TEST ?? 'example.com';
  const results = await Promise.all([checkDnsContract(domain), checkSslContract(domain)]);
  const failures = results.filter((result) => !result.ok);
  console.table(results);
  if (failures.length > 0) process.exit(1);
}

void main();

CI shape: keep secrets out of logs

name: infrastructure-api-contracts

on:
  pull_request:
  schedule:
    - cron: '17 9 * * *'

jobs:
  contract-smoke:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v2
      - run: bun run scripts/check-ops-tools-contracts.ts
        env:
          OPS_TOOLS_API_KEY: ${{ secrets.OPS_TOOLS_API_KEY }}
          DOMAIN_UNDER_TEST: example.com

How to Use Results Operationally

Keep contract failures out of the same alert channel as infrastructure findings. If the DNS contract fails, the platform automation owner should inspect the parser, SDK version, and OpenAPI diff. If the DNS contract passes but the expected record is wrong, the domain owner should fix the record. Mixing the two creates noisy on-call rotations and slow incident response.

Usage endpoints deserve their own checks. A domain portfolio monitor can pass every DNS and SSL assertion but still become expensive if it loops over too many records or repeats cached checks unnecessarily. Validate that usage rows include date, service, count, and user ID, then export usage for monthly finance review.

Where AI Agents and MCP Patterns Fit

AI agents can make contract testing more useful when they are boxed in. An agent can read the OpenAPI document, propose a check plan, summarize failed fields, or draft a pull request comment for the automation owner. It should not get broad authority to scan arbitrary hosts, rotate API keys, or change production thresholds. Treat agent use as orchestration around documented API calls, not as a native Ops.Tools feature unless the product documentation says so.

The safe pattern is approval first, execution second. Ask the agent for the list of endpoints, domains, record types, ports, expected fields, and credit budget. Review the plan, then let a normal CI job or worker run the checks. The result is still a conventional API workflow with human-approved scope, but the boring review work gets easier to manage.

Implementation Boundaries That Keep Tests Useful

The contract suite should not become a second application. Avoid assertions that duplicate every business rule in your monitor. If a certificate must have at least 30 days remaining, that is a policy assertion for the certificate monitor. The contract assertion is simpler: daysRemaining exists and is a number. If your DNS release gate expects an MX host to include a specific provider string, that belongs in the release gate. The contract test only needs to confirm that MX results are returned as an array or documented null.

Keep test data boring too. Use stable assets, avoid domains that change ownership frequently, and isolate experimental checks from production API keys. For port scans, document authorization in the repo next to the manifest. Future maintainers should know why a target is allowed, which ports are approved, and who owns the exception. That record matters when a security reviewer asks why CI is generating network traffic.

Finally, design failure messages for operators, not test authors. "SSL contract failed: certificate missing" is useful. "Expected true to be false" is not. Include endpoint, parameter set, expected field path, observed type, and the owner for the downstream workflow. Redact secrets and trim response samples before they land in CI logs, chat alerts, or tickets.

Decision Matrix

Test layerBest triggerWhat it protects
OpenAPI contract smokePull requests and nightly jobsParsers, SDK usage, dashboards, reports
Release validationDeployments and config changesDNS, SSL, headers, approved ports
Recurring monitorHourly, daily, or weekly schedulePortfolio drift, renewals, exposure review
Incident collectorOn-call runbook or security alertEvidence capture during triage

FAQ

How is this different from a DNS or SSL release gate?

A release gate asks whether production infrastructure is in the expected state. A contract test asks whether the API response still matches the documented shape your release gate, monitor, report, or incident workflow parses.

Should contract tests call every endpoint on every pull request?

Usually no. Run a small smoke set on pull requests, then run broader scheduled checks against approved assets. Port scans and other active checks should stay scoped to targets your organization is authorized to test.

Can Postman replace OpenAPI contract tests?

Postman collections can be part of the workflow, and the Ops.Tools pricing page lists a Postman collection. Keep OpenAPI as the source of truth for response shape and use collections or scripts as runners when they fit your team.

Do these tests require native SIEM or ticketing integrations?

No. The workflow can run in CI, cron, a worker, or a monitoring job. Route failures by your own webhook, issue tracker, SIEM collector, email process, or deployment policy.

Recommended Next Step

Build release and monitoring workflows on a documented API contract

Use Ops.Tools API docs, JSON responses, usage reporting, and generated SDK support to validate the contract before DNS, WHOIS, IP, SSL, HTTP, and port checks become production automation.

Keep reading

Related articles

More guides from the same operational area.