API Evangelist API Evangelist
Learnings
Guidance
Toolbox
Alignment
API Evangelist LLC

Tyk and the Quiet Superpower of Extending OpenAPI

June 26th, 2026 ·
Tyk and the Quiet Superpower of Extending OpenAPI

Extending the OpenAPI specification is a widely used, but seldom talked about superpower of the specification. People who aren’t in the know hit the wall with what the specification can’t do, and they move on and create a new specification — where those in the know understand the specification has become the lingua franca of API operations over the last 16 years, and craft their own extensions for the specification to make it do what they need it to do.

I remember hearing that Tyk had gone all in on the OpenAPI specification, but honestly I was too busy with whatever my hustle was in the moment to actually tune in. It happens. Now I have the time and the interest to learn more about their approach, and I wanted to break down the schema for how Tyk is extending the OpenAPI specification — but also look at a robust example of how the API gateway provider has tackled making API operations declarative.

Tyk’s extension lives under a single vendor extension key at the root of an OpenAPI 3 document: x-tyk-api-gateway. Everything Tyk needs to run an API as a gateway — but that OpenAPI itself has no opinion about — is tucked into that one object. The OpenAPI portion of the document stays clean and portable, fully usable by any other OpenAPI-aware tool, while the gateway-specific concerns sit alongside it in their own namespace. They organize that namespace into four primary sections — info, server, upstream, and middleware — plus an errorOverrides section, and they’ve deliberately kept it minimal, so anything your API doesn’t need can simply be left out.

The Schema

Here is a distilled JSON Schema for the entire x-tyk-api-gateway extension, rendered in YAML. This mirrors the canonical schema Tyk ships in their gateway source (apidef/oas/schema/x-tyk-api-gateway.json), collapsed to its meaningful shape so you can read it in one sitting rather than chasing $refs across a thousand lines.

$schema: "http://json-schema.org/draft-07/schema#"
title: X-Tyk-API-Gateway
description: >-
  The Tyk Vendor Extension. A single object placed at the root of an OpenAPI 3
  document under the key `x-tyk-api-gateway`, carrying all gateway configuration
  that OpenAPI itself does not describe.
type: object
additionalProperties: false
required:
  - info
  - upstream
  - server
properties:

  # ------------------------------------------------------------------
  # INFO — metadata Tyk uses to manage the API proxy
  # ------------------------------------------------------------------
  info:
    type: object
    additionalProperties: false
    required: [name, state]
    properties:
      id:          { type: string, description: Unique API identifier within Tyk }
      dbId:        { type: string, description: Internal database object id }
      orgId:       { type: string, description: Owning organization id }
      name:        { type: string, description: Human-readable API name }
      expiration:  { type: string, description: RFC3339 date when the API expires }
      state:
        type: object
        additionalProperties: false
        properties:
          active:   { type: boolean, description: Whether the API is live and proxying }
          internal: { type: boolean, description: Hide from external routing; internal-only }
      versioning:
        type: object
        additionalProperties: false
        properties:
          enabled:         { type: boolean }
          name:            { type: string }
          default:         { type: string, description: Default version name }
          location:        { type: string, enum: [header, url-param, url] }
          key:             { type: string, description: Header or param carrying the version }
          stripVersioningData: { type: boolean }
          fallbackToDefault:   { type: boolean }
          versions:
            type: array
            items:
              type: object
              properties:
                name: { type: string }
                id:   { type: string }

  # ------------------------------------------------------------------
  # SERVER — the client-to-gateway integration
  # ------------------------------------------------------------------
  server:
    type: object
    additionalProperties: false
    required: [listenPath]
    properties:
      listenPath:
        type: object
        required: [value]
        properties:
          value: { type: string, description: Base path Tyk listens on, e.g. /my-api/ }
          strip: { type: boolean, description: Strip the listen path before proxying upstream }
      authentication:
        type: object
        properties:
          enabled:               { type: boolean }
          stripAuthorizationData: { type: boolean }
          baseIdentityProvider:  { type: string }
          hmac:           { type: object }
          oidc:           { type: object }
          custom:         { type: object }
          securitySchemes:
            type: object
            description: >-
              Maps OpenAPI securitySchemes to Tyk auth (token, jwt, oauth,
              basic, hmac, custom, externalOAuth) with token location and config.
      clientCertificates:
        type: object
        properties:
          enabled:   { type: boolean }
          allowlist: { type: array, items: { type: string } }
      gatewayTags:
        type: object
        properties:
          enabled: { type: boolean }
          tags:    { type: array, items: { type: string } }
      customDomain:
        type: object
        properties:
          enabled:      { type: boolean }
          name:         { type: string }
          certificates: { type: array, items: { type: string } }
      detailedActivityLogs: { type: object, properties: { enabled: { type: boolean } } }
      detailedTracing:      { type: object, properties: { enabled: { type: boolean } } }
      eventHandlers:        { type: array, items: { type: object } }
      ipAccessControl:
        type: object
        properties:
          enabled: { type: boolean }
          allow:   { type: array, items: { type: string } }
          block:   { type: array, items: { type: string } }
      batchProcessing: { type: object, properties: { enabled: { type: boolean } } }
      protocol: { type: string, enum: [http, https, h2c, tcp, tls] }
      port:     { type: integer, minimum: 1, maximum: 65535 }

  # ------------------------------------------------------------------
  # UPSTREAM — the gateway-to-backend integration
  # ------------------------------------------------------------------
  upstream:
    type: object
    additionalProperties: false
    description: Requires either `url` or `loadBalancing`.
    properties:
      url: { type: string, description: Upstream/backend target URL }
      serviceDiscovery:
        type: object
        properties:
          enabled:           { type: boolean }
          queryEndpoint:     { type: string }
          dataPath:          { type: string }
          useTargetList:     { type: boolean }
          cache:             { type: object }
      uptimeTests:
        type: object
        properties:
          enabled: { type: boolean }
          tests:   { type: array, items: { type: object } }
      mutualTLS:
        type: object
        properties:
          enabled:            { type: boolean }
          domainToCertificates: { type: array, items: { type: object } }
      certificatePinning:
        type: object
        properties:
          enabled:                  { type: boolean }
          domainToPublicKeysMapping: { type: array, items: { type: object } }
      rateLimit:
        type: object
        properties:
          enabled:  { type: boolean }
          rate:     { type: integer }
          per:      { type: string, description: Interval, e.g. 60s, 1m, 1h }
      authentication:
        type: object
        description: Auth Tyk presents to the upstream (basic, oauth, request signing)
        properties:
          enabled: { type: boolean }
          basicAuth:      { type: object }
          oauth:          { type: object }
          requestSigning: { type: object }
      loadBalancing:
        type: object
        properties:
          enabled: { type: boolean }
          targets:
            type: array
            items:
              type: object
              properties:
                url:    { type: string }
                weight: { type: integer }
      preserveHostHeader:   { type: object, properties: { enabled: { type: boolean } } }
      preserveTrailingSlash: { type: boolean }
      tlsTransport:
        type: object
        properties:
          insecureSkipVerify: { type: boolean }
          minVersion:         { type: string }
          maxVersion:         { type: string }
          ciphers:            { type: array, items: { type: string } }
      proxy: { type: object, description: Outbound proxy used to reach the upstream }

  # ------------------------------------------------------------------
  # MIDDLEWARE — the request/response processing chain
  # ------------------------------------------------------------------
  middleware:
    type: object
    additionalProperties: false
    properties:
      global:
        type: object
        description: Middleware applied to every endpoint on the API
        properties:
          pluginConfig:   { type: object, description: Custom plugin driver + bundle config }
          cors:
            type: object
            properties:
              enabled:           { type: boolean }
              allowedOrigins:    { type: array, items: { type: string } }
              allowedMethods:    { type: array, items: { type: string } }
              allowedHeaders:    { type: array, items: { type: string } }
              exposedHeaders:    { type: array, items: { type: string } }
              allowCredentials:  { type: boolean }
              maxAge:            { type: integer }
              optionsPassthrough: { type: boolean }
          cache:
            type: object
            properties:
              enabled:               { type: boolean }
              timeout:               { type: integer }
              cacheAllSafeRequests:  { type: boolean }
              cacheResponseCodes:    { type: array, items: { type: integer } }
          prePlugins:                  { type: array, items: { type: object } }
          postAuthenticationPlugins:   { type: array, items: { type: object } }
          postPlugins:                 { type: array, items: { type: object } }
          responsePlugins:             { type: array, items: { type: object } }
          transformRequestHeaders:     { type: object }
          transformResponseHeaders:    { type: object }
          contextVariables: { type: object, properties: { enabled: { type: boolean } } }
          trafficLogs:      { type: object, properties: { enabled: { type: boolean } } }
          requestSizeLimit: { type: object, properties: { enabled: { type: boolean }, value: { type: integer } } }
          ignoreCase:    { type: object, properties: { enabled: { type: boolean } } }
          skipRateLimit: { type: boolean }
          skipQuota:     { type: boolean }
      operations:
        type: object
        description: >-
          Per-endpoint middleware keyed by the OpenAPI operationId. Each entry
          may set any of the fields below.
        additionalProperties:
          type: object   # keyed by operationId
          properties:
            allow:                { type: object, properties: { enabled: { type: boolean } } }
            block:                { type: object, properties: { enabled: { type: boolean } } }
            ignoreAuthentication: { type: object, properties: { enabled: { type: boolean } } }
            internalEndpoint:     { type: object, properties: { enabled: { type: boolean } } }
            validateRequest:
              type: object
              properties:
                enabled:           { type: boolean }
                errorResponseCode: { type: integer }
            mockResponse:
              type: object
              properties:
                enabled:          { type: boolean }
                code:             { type: integer }
                body:             { type: string }
                headers:          { type: array, items: { type: object } }
                fromOASExamples:  { type: object }
            transformRequestMethod:
              type: object
              properties:
                enabled:   { type: boolean }
                toMethod:  { type: string }
            transformRequestBody:
              type: object
              properties:
                enabled: { type: boolean }
                format:  { type: string, enum: [xml, json] }
                body:    { type: string, description: Base64 template }
                path:    { type: string }
            transformResponseBody:
              type: object
              properties:
                enabled: { type: boolean }
                format:  { type: string, enum: [xml, json] }
                body:    { type: string }
                path:    { type: string }
            transformRequestHeaders:
              type: object
              properties:
                enabled: { type: boolean }
                add:     { type: array, items: { type: object } }
                remove:  { type: array, items: { type: string } }
            transformResponseHeaders:
              type: object
              properties:
                enabled: { type: boolean }
                add:     { type: array, items: { type: object } }
                remove:  { type: array, items: { type: string } }
            cache:
              type: object
              properties:
                enabled:              { type: boolean }
                cacheResponseCodes:   { type: array, items: { type: integer } }
                timeout:              { type: integer }
            enforceTimeout:
              type: object
              properties:
                enabled: { type: boolean }
                value:   { type: integer, description: Timeout in seconds }
            rateLimit:
              type: object
              properties:
                enabled: { type: boolean }
                rate:    { type: integer }
                per:     { type: string }
            urlRewrite:
              type: object
              properties:
                enabled:  { type: boolean }
                pattern:  { type: string }
                rewriteTo: { type: string }
                triggers:  { type: array, items: { type: object } }
            virtualEndpoint:
              type: object
              properties:
                enabled:        { type: boolean }
                functionName:   { type: string }
                body:           { type: string, description: Base64 JS source }
                proxyOnError:   { type: boolean }
                requireSession: { type: boolean }
            circuitBreaker:
              type: object
              properties:
                enabled:            { type: boolean }
                threshold:          { type: number }
                sampleSize:         { type: integer }
                coolDownPeriod:     { type: integer }
                halfOpenStateEnabled: { type: boolean }
            trackEndpoint:       { type: object, properties: { enabled: { type: boolean } } }
            doNotTrackEndpoint:  { type: object, properties: { enabled: { type: boolean } } }
            requestSizeLimit:
              type: object
              properties:
                enabled: { type: boolean }
                value:   { type: integer }

  # ------------------------------------------------------------------
  # ERROR OVERRIDES — customize gateway error responses
  # ------------------------------------------------------------------
  errorOverrides:
    type: object
    properties:
      enabled: { type: boolean }
      value:
        type: array
        items:
          type: object
          properties:
            code:    { type: integer }
            match:   { type: string }
            response:
              type: object
              properties:
                statusCode: { type: integer }
                body:       { type: string }
                headers:    { type: array, items: { type: object } }

A few design choices are worth noticing. Nearly every feature is an object with its own enabled flag rather than a bare value — which is what lets Tyk keep the extension minimal. You can declare a feature, leave it disabled, and turn it on later without restructuring the document. The middleware.operations map keys directly off the standard OpenAPI operationId, so the Tyk configuration binds to your existing OpenAPI paths instead of duplicating them. And the four-section split — who calls you (server), who you call (upstream), what happens in between (middleware), and what the thing is (info) — is a clean mental model for the entire life of a request.

A Comprehensive Example

Here’s a single OpenAPI 3 document that exercises a wide swath of the extension: token auth, CORS, a custom listen path, an upstream with rate limiting, API versioning, and a set of per-operation middleware including request validation, a mock response, caching, a URL rewrite, and an enforced timeout. The OpenAPI half is deliberately ordinary — the point is how x-tyk-api-gateway rides alongside it.

openapi: 3.0.3
info:
  title: Widget API
  version: 1.0.0
  description: A small catalog API, fronted by Tyk.

servers:
  - url: https://api.example.com/widgets/

paths:
  /widgets:
    get:
      operationId: listWidgets
      summary: List all widgets
      responses:
        "200":
          description: A list of widgets
          content:
            application/json:
              schema:
                type: array
                items: { $ref: "#/components/schemas/Widget" }
    post:
      operationId: createWidget
      summary: Create a widget
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/Widget" }
      responses:
        "201": { description: Created }

  /widgets/{id}:
    get:
      operationId: getWidget
      summary: Get a single widget
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: A widget
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Widget" }
              examples:
                sample:
                  value: { id: "demo-1", name: "Demo Widget", price: 9.99 }

  /health:
    get:
      operationId: healthCheck
      summary: Liveness probe
      responses:
        "200": { description: OK }

components:
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: Authorization
  schemas:
    Widget:
      type: object
      required: [name]
      properties:
        id:    { type: string }
        name:  { type: string }
        price: { type: number }

# ====================================================================
# The Tyk Vendor Extension
# ====================================================================
x-tyk-api-gateway:

  info:
    name: Widget API
    state:
      active: true
      internal: false
    versioning:
      enabled: true
      name: v1
      default: v1
      location: header
      key: x-api-version
      fallbackToDefault: true
      versions:
        - name: v1
          id: widget-api-v1

  server:
    listenPath:
      value: /widgets/
      strip: true
    protocol: http
    port: 8080
    authentication:
      enabled: true
      stripAuthorizationData: true
      securitySchemes:
        apiKey:
          enabled: true
          header:
            enabled: true
            name: Authorization
    cors:
      enabled: true
    ipAccessControl:
      enabled: false
    detailedTracing:
      enabled: true

  upstream:
    url: http://widgets.internal:9000/
    rateLimit:
      enabled: true
      rate: 100
      per: 60s
    uptimeTests:
      enabled: true
    tlsTransport:
      minVersion: "1.2"

  middleware:
    global:
      cors:
        enabled: true
        allowedOrigins: ["https://app.example.com"]
        allowedMethods: [GET, POST, OPTIONS]
        allowedHeaders: [Authorization, Content-Type]
        allowCredentials: true
        maxAge: 3600
      cache:
        enabled: true
        timeout: 30
        cacheAllSafeRequests: false
        cacheResponseCodes: [200]
      trafficLogs:
        enabled: true

    operations:

      # Validate incoming POST bodies against the Widget schema
      createWidget:
        validateRequest:
          enabled: true
          errorResponseCode: 422
        rateLimit:
          enabled: true
          rate: 10
          per: 60s

      # Cache the read-heavy list endpoint at the edge
      listWidgets:
        cache:
          enabled: true
          cacheResponseCodes: [200]
          timeout: 60
        enforceTimeout:
          enabled: true
          value: 5

      # Serve a canned response straight from the OAS example — no upstream call
      getWidget:
        mockResponse:
          enabled: true
          fromOASExamples:
            enabled: true
            code: 200
            contentType: application/json

      # Rewrite the public /health path to the upstream's /status endpoint,
      # and let it through without authentication
      healthCheck:
        ignoreAuthentication:
          enabled: true
        urlRewrite:
          enabled: true
          pattern: "/health$"
          rewriteTo: "/status"

  errorOverrides:
    enabled: true
    value:
      - code: 404
        match: "not found"
        response:
          statusCode: 404
          body: '{"error":"That widget does not exist."}'
          headers:
            - name: Content-Type
              value: application/json

Read it top to bottom and the whole operational posture of the API is right there in front of you. The info block says what this is and how it’s versioned. The server block says who’s allowed to call it and how they authenticate. The upstream block says where the real service lives and how hard clients are allowed to hit it. And the middleware.operations map — keyed to the very same operationIds declared up in paths — says exactly what Tyk should do on each route: validate createWidget, cache and time-box listWidgets, mock getWidget directly from its OpenAPI example, and quietly rewrite and un-authenticate the health check. None of that lives in a separate config store or a console UI you have to screenshot to document. It lives in the API definition.

Why This Matters

This is what extending OpenAPI is supposed to look like. Tyk didn’t fork the format, didn’t invent a parallel DSL, and didn’t ask you to maintain a second file that drifts out of sync with your spec. They took the one document teams already write — the OpenAPI definition — and gave it a place to carry the operational truth that OpenAPI was never designed to hold. The describe-your-API layer and the run-your-API layer finally live in the same file, under the same version control, reviewed in the same pull request.

The payoff is that API operations become declarative. Instead of clicking through a dashboard to configure rate limits, auth, caching, and rewrites — and then trying to remember to document all of it somewhere else — you write it down once, in a schema-validated artifact, and the gateway becomes a function of that artifact. That’s GitOps for the API layer. It’s diffable, it’s portable, and it’s auditable. And because the OpenAPI portion stays clean and standards-compliant, every other tool in your pipeline — docs generators, mock servers, SDK generators, linters — keeps working, blissfully ignorant of the Tyk extension sitting next to it.

It’s also a quiet vote of confidence in OpenAPI as the lingua franca of API operations. Tyk could have decided the specification wasn’t enough and walked away to build their own thing. Instead they bet that the gravity of OpenAPI — sixteen years of tooling, mindshare, and muscle memory — was worth more than the freedom of a clean-sheet format, and they extended it to do what they needed. That’s the move the people in the know keep making, and it’s why the specification keeps absorbing more of the API lifecycle every year. Tyk’s x-tyk-api-gateway is a particularly complete example of the pattern, and a good argument for why you should reach for an extension before you reach for a whole new spec.