HIP Platform - OpenAPI 3.1 Support Decision

Date: 2026-02-16
Status: Analysis Complete
Version: 1.0
Primary Question: What do we want to support from OAS 3.1?


Executive Summary

Answer: Support OAS 3.1 selectively - implement JSON Schema 2020-12, hold on webhooks pending blocker validation, discourage complex features.

Key Decisions:

  1. JSON Schema 2020-12: ✅ SUPPORT
  2. Webhooks: ⏸️ HOLD (pending blocker validation)
  3. Discriminators & Union Types: ❌ DISCOURAGE

Examples have been included of the sort of thing that can be expressed with JSON schema 2020-12. These are intended only to illustrate what the schema can do and are not a judgement of any API seen at this point. We merely need to be deliberate about what we want to support, and discourage things we do no not.


Feature Decision Matrix

FeatureOAS 3.1 CapabilityBusiness ValueDecisionRationale
JSON Schema 2020-12Full JSON Schema standardHIGH✅ SUPPORTBetter validation, improved tooling interop
WebhooksNative async callbacksHIGH⏸️ HOLDComplex, depends on blockers (see OAS-3.1-WEBHOOKS.md)
info.license.identifierSPDX license identifiersMEDIUM✅ SUPPORTMetadata only, low effort
unevaluatedPropertiesAdvanced schema compositionMEDIUM⚠️ OPTIONALUse sparingly for complex inheritance
$ref SiblingsAllow alongside other propertiesLOW✅ WORKSAlready compatible with 3.0
Improved DiscriminatorsClearer polymorphismLOW❌ DISCOURAGEUse RESTful patterns instead
Union Typestype: [T, "null"] patternsLOW❌ DISCOURAGECode smell - indicates design issues

1. JSON Schema 2020-12 (SUPPORT)

Decision: ✅ Support OAS 3.1’s full JSON Schema 2020-12 standard

What’s New: OAS 3.0 used JSON Schema Draft 5 (from 2017). OAS 3.1 upgrades to the full JSON Schema 2020-12 standard, removing OAS-specific keywords like nullable and enabling richer validation capabilities.

Why: Enables better validation capabilities and improved tooling interoperability.

Constraint: With domain APIs we intend to support simple REST aligned APIs (no deep nesting, complex conditionals, or advanced patterns). Some of the features of JSON schema 2020-12, whilst technically correct & can add value for an API producer, reduce the ease for the consumer, and especially for ‘re-usable’ APIs that may not be the right tradeoff.

Encouraged: Simple & Clear

Example 1: Basic types with validation

Pattern matching and min/max constraints are standard JSON Schema features now fully supported in 3.1 (OAS 3.0 had these via Draft 5 subset, but 3.1 enables the full 2020-12 standard).

{
  "type": "object",
  "required": ["periodKey", "dutyAmount"],
  "properties": {
    "periodKey": {
      "type": "string",
      "pattern": "^[0-9]{4}[AB]$"
    },
    "dutyAmount": {
      "type": "number",
      "minimum": 0,
      "maximum": 999999999.99
    }
  }
}

Example 2: Enums for fixed values

Enums are a standard JSON Schema feature to restrict values to a fixed set. OAS 3.1 enables richer enum validation with the full 2020-12 standard.

{
  "complianceStatus": {
    "type": "string",
    "enum": ["PENDING", "COMPLIANT", "NON_COMPLIANT"]
  }
}

Why this works:

  • ✅ Clear types (no ambiguity)
  • ✅ Simple validation rules
  • ✅ Easy to code-generate

Discouraged: unevaluatedProperties (Complex Inheritance)

What’s New: OAS 3.1 introduces unevaluatedProperties (from JSON Schema 2020-12), which validates that no unexpected properties are present after evaluating all schema constraints. This is powerful but can lead to overly complex schemas.

Key Issue: When using unevaluatedProperties: false with multiple allOf references, it becomes hard for humans (and code generators) to understand what fields are actually valid/required. The field definitions are scattered across referenced schemas. If this pattern is deemed necessary, consider tooling to generate a combined view of all referenced schemas merged together.

Problem: Using it with multiple allOf schemas scatters field definitions, making them unclear.

DON’T:

{
  "unevaluatedProperties": false,
  "allOf": [
    { "$ref": "#/components/schemas/BaseSubmission" },
    { "$ref": "#/components/schemas/AuditFields" },
    { "$ref": "#/components/schemas/ComplianceFields" }
  ]
}

✅ DO: Flat structure

{
  "type": "object",
  "required": ["submissionId", "periodKey", "dutyAmount"],
  "properties": {
    "submissionId": { "type": "string" },
    "periodKey": { "type": "string" },
    "dutyAmount": { "type": "number" },
    "auditedBy": { "type": ["string", "null"] },
    "complianceStatus": { "enum": ["PENDING", "COMPLIANT", "NON_COMPLIANT"] }
  }
}

Why: All fields visible in one place, clear what’s required.


2. Webhooks (HOLD)

Decision: ⏸️ HOLD pending validation of three critical blockers

Why: Webhooks have high business value (enterprise async pattern 5a) but implementation is complex and depends on factors outside platform team control.

Critical Blockers

BlockerImpactOwner
HOD backends can publish to event busWebhooks require event bus for delivery - if HOD teams cannot publish events, webhooks are not viableHOD teams
Consumer interest >30%Validates market demand for webhooks vs polling - if interest is low, investment not justifiedConsumer survey
Platform team capacityRequires 6-8 weeks for MVP service build plus ongoing maintenance - if unavailable, deferPlatform leadership

Go/No-Go: Proceed to implementation ONLY if ALL three blockers are cleared. If any single blocker fails, defer webhooks.

See detailed analysis: OAS-3.1-WEBHOOKS.md for complete evaluation and blockers framework.


3. Discriminators & Union Types (DISCOURAGE)

Decision: ❌ Discourage both features

Improved Discriminators (DISCOURAGE)

What’s New: OAS 3.1 improved the discriminator feature to better handle polymorphic requests/responses by using the value of a specific property to determine which schema applies.

Example with discriminator (showing why it’s problematic):

The two variant schemas:

// OriginalVpdReturn schema
{
  "type": "object",
  "required": ["returnType", "vpdApprovalNumber", "dutyAmount"],
  "properties": {
    "returnType": { "enum": ["ORIGINAL"] },
    "vpdApprovalNumber": { "type": "string" },
    "dutyAmount": { "type": "number" }
  }
}
 
// AmendedVpdReturn schema
{
  "type": "object",
  "required": ["returnType", "vpdApprovalNumber", "originalAcknowledgementReference", "revisedDutyAmount"],
  "properties": {
    "returnType": { "enum": ["AMENDED"] },
    "vpdApprovalNumber": { "type": "string" },
    "originalAcknowledgementReference": { "type": "string" },
    "revisedDutyAmount": { "type": "number" }
  }
}

The discriminator schema:

oneOf:
  - $ref: '#/components/schemas/OriginalVpdReturn'
  - $ref: '#/components/schemas/AmendedVpdReturn'
discriminator:
  propertyName: returnType
  mapping:
    ORIGINAL: '#/components/schemas/OriginalVpdReturn'
    AMENDED: '#/components/schemas/AmendedVpdReturn'

Consumer requests (must choose which to send):

// Option 1: Original
POST /vpd/submission-returns/v1
{ "returnType": "ORIGINAL", "vpdApprovalNumber": "VPD123456", "dutyAmount": 5000 }
 
// Option 2: Amended
POST /vpd/submission-returns/v1
{ "returnType": "AMENDED", "vpdApprovalNumber": "VPD123456", "originalAcknowledgementReference": "ACK-123", "revisedDutyAmount": 5500 }

Problem: Consumer must choose which variant to send by setting returnType. This adds complexity without clear benefit.

✅ DO: Use HTTP methods instead

// Create original return
POST /vpd/submission-returns/v1
{ "vpdApprovalNumber": "VPD123456", "dutyAmount": 5000 }
 
// Amend existing return
PUT /vpd/submission-returns/v1/{acknowledgementReference}
{ "vpdApprovalNumber": "VPD123456", "dutyAmount": 5500 }

Why: HTTP method (POST vs PUT) is the discriminator. No ambiguous fields in payload. Standard REST.

Union Types (DISCOURAGE)

What’s New: OAS 3.1 allows JSON Schema’s array syntax for type unions (e.g., type: [string, number]), replacing OAS 3.0’s workarounds with oneOf. (Note: OAS 3.0 also had nullable: true for nullable fields, which 3.1 replaces with type: ["string", "null"])

Problem: type: [string, number, integer] allows multiple incompatible types, indicating unclear design.

DON’T:

"dutyReference": { "type": [string, number, integer] }

✅ DO: Pick one type or use separate fields

Option 1 - Single type: Capture as a string and enforce format with a pattern. This handles alphanumeric references and is flexible enough for future changes without breaking consumers.

"dutyApprovalRef": { "type": "string", "pattern": "^[A-Z0-9]+$" }

Option 2 - Separate fields:

"dutyReferenceAlpha": { "type": "string" },
"dutyAmountPence": { "type": "integer" }

Option 3 - Nullable fields are GOOD (explicitly marked): Use string "null" (not unquoted null) in the type array. This makes optionality explicit and clear.

"approverComments": { "type": ["string", "null"] },
"rejectionReason": { "type": ["string", "null"], "enum": ["INVALID_DUTY", null] }

Note: type: [T, "null"] is encouraged for optional fields. The problem is incompatible types like [string, number].


4. Features Already Working

$ref Siblings

Status: Works in both 3.0 and 3.1 - no action needed. Keep using allOf pattern (reliable in both versions).

info.license.identifier

Status: Add SPDX license identifiers to platform spec template (metadata only, low effort).


Recommendations

Immediate (enable JSON Schema 2020-12):

  • Integrate OAS 3.1-compatible validator library
  • Update Integration Hub to accept OAS 3.1 specs
  • Publish this decision to producers

Webhooks - Before any investment:

  • Survey consumer community (>30% threshold)
  • Engage HOD teams (validate event bus publishing capability)
  • Assess platform team capacity (6-8 weeks available?)

If all webhook blockers cleared → Proceed with implementation If any blocker fails → Document learnings and defer


FAQ

Q: Do I need to migrate from OAS 3.0 to OAS 3.1? A: No. Both versions are fully supported. Choose based on your API needs.

Q: Will my OAS 3.0 API work if other APIs use OAS 3.1? A: Yes, completely. From a consumer perspective, OAS version is transparent at runtime.

Q: What about webhooks - when are they available? A: Under evaluation. See OAS-3.1-WEBHOOKS.md for blocker framework and timeline.