Managing OpenAPI Specifications
OpenAPI specifications are the foundation of contract testing in Entente. As a provider, your OpenAPI spec defines the contract that consumers will test against and that you’ll verify your implementation matches.
Creating OpenAPI Specifications
Section titled “Creating OpenAPI Specifications”Basic Structure
Section titled “Basic Structure”Here’s the castle-service OpenAPI specification as a real example:
{ "openapi": "3.0.3", "info": { "title": "Castle Service API", "version": "1.0.0", "description": "A simple API for managing French castles" }, "servers": [ { "url": "http://localhost:4001", "description": "Development server" } ], "paths": { "/castles": { "get": { "operationId": "listCastles", "summary": "List all castles", "responses": { "200": { "description": "List of castles", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Castle" } }, "example": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Château de Versailles", "region": "Île-de-France", "yearBuilt": 1623 } ] } } } } }, "post": { "operationId": "createCastle", "summary": "Create a new castle", "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateCastleRequest" } } } }, "responses": { "201": { "description": "Castle created successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Castle" } } } }, "400": { "description": "Invalid input", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } }, "/castles/{id}": { "get": { "operationId": "getCastle", "summary": "Get a castle by ID", "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Castle details", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Castle" } } } }, "404": { "description": "Castle not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } }, "delete": { "operationId": "deleteCastle", "summary": "Delete a castle", "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "204": { "description": "Castle deleted successfully" }, "404": { "description": "Castle not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } } }, "components": { "schemas": { "Castle": { "type": "object", "required": ["id", "name", "region", "yearBuilt"], "properties": { "id": { "type": "string", "description": "Unique identifier for the castle" }, "name": { "type": "string", "description": "Name of the castle" }, "region": { "type": "string", "description": "French region where the castle is located" }, "yearBuilt": { "type": "integer", "minimum": 1000, "maximum": 2100, "description": "Year the castle was built" }, "description": { "type": "string", "description": "Optional description of the castle" } } }, "CreateCastleRequest": { "type": "object", "required": ["name", "region", "yearBuilt"], "properties": { "name": { "type": "string", "minLength": 1, "description": "Name of the castle" }, "region": { "type": "string", "minLength": 1, "description": "French region where the castle is located" }, "yearBuilt": { "type": "integer", "minimum": 1000, "maximum": 2100, "description": "Year the castle was built" }, "description": { "type": "string", "description": "Optional description of the castle" } } }, "Error": { "type": "object", "required": ["error", "message"], "properties": { "error": { "type": "string", "description": "Error code" }, "message": { "type": "string", "description": "Human-readable error message" } } } } }}Best Practices for OpenAPI Specs
Section titled “Best Practices for OpenAPI Specs”1. Use Meaningful Operation IDs
{ "get": { "operationId": "listCastles", // ✅ Clear, descriptive "summary": "List all castles" }}2. Include Realistic Examples
{ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Castle" }, "example": { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Château de Versailles", "region": "Île-de-France", "yearBuilt": 1623 } } }}3. Define All Response Codes
{ "responses": { "200": { "description": "Success" }, "400": { "description": "Bad request" }, "404": { "description": "Not found" }, "500": { "description": "Server error" } }}4. Use Detailed Schema Validation
{ "yearBuilt": { "type": "integer", "minimum": 1000, "maximum": 2100, "description": "Year the castle was built" }}Uploading Specifications
Section titled “Uploading Specifications”Using the CLI
Section titled “Using the CLI”Upload your OpenAPI spec to Entente using the CLI:
# Upload spec for development environmententente upload-spec \ --service castle-service \ --version 0.1.0 \ --environment development \ --spec ./spec/openapi.json \ --branch main
# Upload for staging environmententente upload-spec \ --service castle-service \ --version 0.1.0 \ --environment staging \ --spec ./spec/openapi.json \ --branch mainDuring Service Registration
Section titled “During Service Registration”You can upload specs when registering your service:
entente register-service \ --type provider \ --service castle-service \ --spec ./spec/openapi.json \ --spec-version 0.1.0 \ --environment developmentPackage.json Scripts
Section titled “Package.json Scripts”Add convenient scripts to your package.json:
{ "scripts": { "upload:spec": "entente upload-spec --service castle-service --version 0.1.0 --environment test --spec spec/openapi.json", "register:provider": "entente register-service --type provider --service castle-service --spec spec/openapi.json --spec-version 0.1.0 --environment test" }}Spec Versioning
Section titled “Spec Versioning”Environment-Based Versioning
Section titled “Environment-Based Versioning”Entente manages specs per environment, allowing different versions in each environment:
graph TD
subgraph "Development"
D1[castle-service v0.2.0-dev]
D2[OpenAPI Spec v0.2.0]
D1 --> D2
end
subgraph "Staging"
S1[castle-service v0.1.5]
S2[OpenAPI Spec v0.1.5]
S1 --> S2
end
subgraph "Production"
P1[castle-service v0.1.0]
P2[OpenAPI Spec v0.1.0]
P1 --> P2
end
Development --> Staging
Staging --> Production
Version Compatibility
Section titled “Version Compatibility”When consumers request a provider spec, Entente:
- Checks for exact version match in the requested environment
- Falls back to latest version if exact match not found
- Warns if using a different version than requested
- Fails if no compatible version exists
Example consumer request:
// Consumer requests castle-service v0.1.0const mock = await client.createMock('castle-service', '0.1.0')
// Entente may use v0.1.5 if v0.1.0 not deployed// and will log: "Using latest provider version: 0.1.5 for castle-service"Integration with Development
Section titled “Integration with Development”Local Development
Section titled “Local Development”Keep your spec file alongside your code:
castle-service/├── src/│ └── index.ts├── spec/│ └── openapi.json # Your OpenAPI spec├── test/│ └── provider.test.ts└── package.jsonAuto-Upload in CI
Section titled “Auto-Upload in CI”Automatically upload specs during CI/CD:
- name: Upload OpenAPI Spec env: ENTENTE_API_KEY: ${{ secrets.ENTENTE_API_KEY }} run: | entente upload-spec \ --service castle-service \ --version $(node -p "require('./package.json').version") \ --environment development \ --spec spec/openapi.jsonSwagger UI Integration
Section titled “Swagger UI Integration”You can serve your OpenAPI spec locally for development:
import { swaggerUI } from '@hono/swagger-ui'import { readFileSync } from 'fs'
const app = new Hono()
// Serve Swagger UIapp.get('/docs', swaggerUI({ url: '/spec'}))
// Serve OpenAPI specapp.get('/spec', (c) => { const spec = JSON.parse(readFileSync('./spec/openapi.json', 'utf-8')) return c.json(spec)})Spec Validation
Section titled “Spec Validation”Local Validation
Section titled “Local Validation”Validate your spec before uploading:
# Using Swagger CLInpx @apidevtools/swagger-cli validate spec/openapi.json
# Using Redoc CLInpx redoc-cli validate spec/openapi.jsonEntente Validation
Section titled “Entente Validation”Entente validates specs during upload:
- Schema compliance with OpenAPI 3.0.3
- Reference resolution (all $ref links work)
- Example validation (examples match schemas)
- Operation ID uniqueness
Common Pitfalls
Section titled “Common Pitfalls”Missing Operation IDs
Section titled “Missing Operation IDs”{ "get": { "operationId": "listCastles", // ✅ Always include this "summary": "List all castles" }}Inconsistent Examples
Section titled “Inconsistent Examples”{ "schema": { "type": "object", "properties": { "yearBuilt": { "type": "integer" } } }, "example": { "yearBuilt": "1623" // ❌ String instead of integer }}Missing Error Responses
Section titled “Missing Error Responses”{ "responses": { "200": { "description": "Success" }, "404": { "description": "Not found" }, // ✅ Include error cases "400": { "description": "Bad request" } }}Next Steps
Section titled “Next Steps”- Provider Verification - Learn how to verify your implementation against the spec
- GitHub Actions - Automate spec uploads in CI/CD
- State Management - Handle test data setup for verification
The OpenAPI specification is the foundation of everything else in Entente, so invest time in creating comprehensive, accurate specs that truly represent your API.