OpenAPI Compliance Analysis
OpenAPI Compliance Analysis
Section titled “OpenAPI Compliance Analysis”Date: 2026-04-29
Scope: openapi.yaml vs src/handlers/* (post RFC 7807 / schema-sync commit)
Method: Line-by-line comparison of spec schemas against TypeScript handler output types
Summary
Section titled “Summary”The spec and implementation are well-aligned after the schema-sync commit.
Ten issues remain, grouped below by severity.
title: “OpenAPI Compliance Analysis”
Section titled “title: “OpenAPI Compliance Analysis””Critical — spec diverges from implementation in ways that mislead consumers
Section titled “Critical — spec diverges from implementation in ways that mislead consumers”1. ClickthroughRow schema is additionalProperties: true (no fields defined)
Section titled “1. ClickthroughRow schema is additionalProperties: true (no fields defined)”Spec: ClickthroughRow has no properties, only additionalProperties: true.
Implementation: handleGetClickthroughs always returns rows with exactly these nine fields:
advertiser_name (string|null)campaign_name (string|null)site_name (string|null)ad_name (string|null)campaign_id (integer)placement_id (integer)day (string, YYYY-MM-DD)event_dest (string)total (integer)Fix: Replace ClickthroughRow with an explicit schema. Keeping additionalProperties: true
means any generated client (SDK, Postman schema, etc.) treats the array as object[] with no
typed fields.
2. Connector metric fields i, iu, c, cu, e, eu have no type
Section titled “2. Connector metric fields i, iu, c, cu, e, eu have no type”Spec (ConnectorRowV1 and ConnectorRowV2): These six fields have description but no type.
Implementation: All six return number (integer counts from aggregate DB).
Fix: Add type: integer to each. Without type, strict OpenAPI validators (Spectral,
openapi-ts, generated clients) treat them as unconstrained JSON Schema, breaking type generation.
3. Retry-After header documented on 429 but never sent
Section titled “3. Retry-After header documented on 429 but never sent”Spec: RateLimitExceeded response documents a Retry-After: integer header.
Implementation: ratelimit.ts throws HttpError(429) immediately. The Durable Object
returns only allowed/count/rph — no window reset timestamp. checkRateLimit() has no way
to compute a meaningful Retry-After value, and the error middleware doesn’t set one.
Options (ranked):
- A (recommended): Remove
Retry-Afterfrom the spec until the DO exposes reset time. - B: Set a conservative
Retry-After: 3600(worst-case window) in the 429 error path inindex.ts. Inaccurate but RFC 7231-compliant. - C: Add a
/resetresponse from the DO that returns seconds-until-reset, compute from that incheckRateLimit(). Accurate but requires DO interface change.
4. info.version: 2.0.0 vs banner X-Adventive-API-Version: 1.0.0
Section titled “4. info.version: 2.0.0 vs banner X-Adventive-API-Version: 1.0.0”Spec: info.version: '2.0.0'
Implementation: X-Adventive-API-Version: 1.0.0
Interpretation: OpenAPI info.version is the spec/document revision. The banner header is
the deployed API version. These can differ by design, but the gap between 1.0.0 and 2.0.0 is
confusing. If the spec version describes API capabilities (v2 routes exist), both should track
the same version string. Recommend: align info.version to '1.0.0' to match the banner, and
bump both together when breaking changes ship.
Moderate — behavioral gaps or misleading parameter documentation
Section titled “Moderate — behavioral gaps or misleading parameter documentation”5. /clickthroughs documents version and data_connector params that have no effect
Section titled “5. /clickthroughs documents version and data_connector params that have no effect”Spec: Both Version and DataConnector are listed as parameters for /clickthroughs.
Implementation: handleGetClickthroughs never calls parseVersion() or parseDataConnector().
Both params are silently ignored.
Fix: Remove them from the /clickthroughs parameter list. Listing them implies they affect
behavior; they don’t.
6. removeZeros has no effect on v1 connector rows
Section titled “6. removeZeros has no effect on v1 connector rows”Spec: RemoveZeros parameter description says “Strip rows / keys where all metrics are zero.”
Implementation (bulkConnectorRows):
- Zero-impression rows are always omitted (hard-coded
impressions === 0guard). - For v2,
removeZeros=1additionally strips zero-valued keys from each row object. - For v1,
removeZeros=1is accepted but does nothing — all fields are always emitted.
Fix: Either apply removeZeros filtering to v1 rows (strip zero-valued numeric keys), or
add a note to the spec: “removeZeros key-stripping only applies to v2 responses.”
7. /openapi.yaml endpoint returns 501, spec documents 200
Section titled “7. /openapi.yaml endpoint returns 501, spec documents 200”Spec: /openapi.yaml has a 200 text/yaml response.
Implementation (src/index.ts:48): Returns 501 Not Implemented with a plain-text body.
This is intentional — the YAML is not bundled into the Worker.
Fix: Add a 501 response entry to the spec path (remove or keep the 200 as aspirational).
Or bundle the YAML at build time (add COPY openapi.yaml in the worker bundle and serve it).
8. AnalyticsResponse.data is always a single-element array
Section titled “8. AnalyticsResponse.data is always a single-element array”Spec: data: array of CampaignAnalytics (unbounded).
Implementation: Handler wraps exactly one campaign result: { data: [result] }.
Not wrong (an array of one is valid), but a consumer would reasonably expect multiple
campaigns per call given the array type. Consider documenting the single-item constraint, or
change the response to data: CampaignAnalytics (non-array).
Minor — spec modeling concerns (don’t affect runtime consumers)
Section titled “Minor — spec modeling concerns (don’t affect runtime consumers)”9. PlacementAnalytics.ad_units uses oneOf without a discriminator
Section titled “9. PlacementAnalytics.ad_units uses oneOf without a discriminator”Spec: ad_units items use oneOf: [AdUnitAnalyticsV1, AdUnitAnalyticsV2].
Problem: AdUnitAnalyticsV2 is a strict superset of AdUnitAnalyticsV1 (allOf extension).
oneOf requires exactly one schema to match — every v2 object also satisfies v1, so validators
will reject all v2 objects.
Fix: Use anyOf instead of oneOf, or add a discriminator property to distinguish v1
from v2 ad unit objects at runtime.
10. version query param enum: ['1.0', '2.0'] is narrower than implementation
Section titled “10. version query param enum: ['1.0', '2.0'] is narrower than implementation”Spec: enum: ['1.0', '2.0']
Implementation: parseVersion() accepts any string starting with '2.' as v2 (e.g., version=2.5).
The path-prefix parser accepts any /v{N}.{minor}/ where N >= 2.
Not a bug — the implementation is intentionally more permissive. But the spec enum will
cause generated validators to reject version=2.5. Fix: document as type: string with an
example and a description, or keep the enum and accept that the server silently upgrades
2.x strings.
Recommended fix priority
Section titled “Recommended fix priority”| # | Fix | Effort |
|---|---|---|
| 2 | Add type: integer to connector metric fields | 5 min |
| 5 | Remove unused params from /clickthroughs | 5 min |
| 4 | Align info.version to 1.0.0 | 1 min |
| 1 | Define ClickthroughRow fields explicitly | 15 min |
| 9 | Change oneOf → anyOf for ad_units | 2 min |
| 10 | Loosen version enum or add note | 5 min |
| 6 | Apply removeZeros to v1 connector or document limitation | 20 min |
| 3 | Remove Retry-After from spec (or add conservative value) | 10 min |
| 7 | Add 501 to /openapi.yaml path or bundle YAML | varies |
| 8 | Document single-item constraint on analytics response | 5 min |