Skip to content

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


The spec and implementation are well-aligned after the schema-sync commit.
Ten issues remain, grouped below by severity.

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-After from the spec until the DO exposes reset time.
  • B: Set a conservative Retry-After: 3600 (worst-case window) in the 429 error path in index.ts. Inaccurate but RFC 7231-compliant.
  • C: Add a /reset response from the DO that returns seconds-until-reset, compute from that in checkRateLimit(). 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 === 0 guard).
  • For v2, removeZeros=1 additionally strips zero-valued keys from each row object.
  • For v1, removeZeros=1 is 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.


#FixEffort
2Add type: integer to connector metric fields5 min
5Remove unused params from /clickthroughs5 min
4Align info.version to 1.0.01 min
1Define ClickthroughRow fields explicitly15 min
9Change oneOfanyOf for ad_units2 min
10Loosen version enum or add note5 min
6Apply removeZeros to v1 connector or document limitation20 min
3Remove Retry-After from spec (or add conservative value)10 min
7Add 501 to /openapi.yaml path or bundle YAMLvaries
8Document single-item constraint on analytics response5 min