{
  "openapi": "3.1.0",
  "info": {
    "title": "mroki API",
    "version": "1.0.0",
    "summary": "REST API for the mroki traffic-testing service.",
    "description": "The REST API provided by `mroki-api`. It manages traffic-testing gates and\nthe requests captured against them, and exposes server configuration and\naggregate statistics.\n\nAll successful responses are wrapped in a `data` envelope. Error responses\nfollow RFC 7807 (Problem Details for HTTP APIs). All endpoints except the\ninfrastructure endpoints (`/health/*`, `/metrics`) require bearer-token\nauthentication.\n",
    "license": {
      "name": "MIT",
      "url": "https://github.com/pedrobarco/mroki/blob/main/LICENSE"
    },
    "contact": {
      "name": "mroki",
      "url": "https://github.com/pedrobarco/mroki"
    }
  },
  "servers": [
    {
      "url": "http://localhost:8090",
      "description": "Local development server"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Config",
      "description": "Read-only, server-wide configuration."
    },
    {
      "name": "Stats",
      "description": "Cross-gate aggregate statistics."
    },
    {
      "name": "Gates",
      "description": "Traffic-testing gates with live and shadow URLs."
    },
    {
      "name": "Requests",
      "description": "Requests captured and compared against a gate."
    },
    {
      "name": "Health",
      "description": "Kubernetes liveness and readiness probes (unauthenticated)."
    },
    {
      "name": "Metrics",
      "description": "Prometheus metrics scrape endpoint (unauthenticated)."
    }
  ],
  "paths": {
    "/config": {
      "get": {
        "operationId": "getConfig",
        "summary": "Get server configuration",
        "description": "Returns the read-only, server-wide configuration the hub needs.",
        "tags": [
          "Config"
        ],
        "responses": {
          "200": {
            "description": "The server configuration.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "title": "ConfigResponse",
                  "description": "Envelope wrapping a single Config object.",
                  "additionalProperties": false,
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "title": "Config",
                      "description": "Read-only, server-wide settings the hub needs to render its UI. It is not\ntied to any gate; it reflects the API's own configuration.\n",
                      "additionalProperties": false,
                      "required": [
                        "retention"
                      ],
                      "properties": {
                        "retention": {
                          "type": "string",
                          "description": "The global retention floor as a Go duration string (e.g. `720h`). Every\ngate is pruned no sooner than this, and any per-gate override must be at\nleast this value.\n",
                          "example": "720h"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed because the bearer token was missing or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          },
          "429": {
            "description": "The client has exceeded the rate limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          },
          "500": {
            "description": "An unexpected error occurred while processing the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          }
        }
      }
    },
    "/stats": {
      "get": {
        "operationId": "getGlobalStats",
        "summary": "Get global statistics",
        "description": "Returns cross-gate aggregate statistics.",
        "tags": [
          "Stats"
        ],
        "responses": {
          "200": {
            "description": "The global statistics.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "title": "GlobalStatsResponse",
                  "description": "Envelope wrapping a single GlobalStats object.",
                  "additionalProperties": false,
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "title": "GlobalStats",
                      "description": "Cross-gate aggregate statistics.",
                      "additionalProperties": false,
                      "required": [
                        "total_gates",
                        "total_requests_24h",
                        "total_diff_rate"
                      ],
                      "properties": {
                        "total_gates": {
                          "type": "integer",
                          "format": "int64",
                          "description": "Total number of gates.",
                          "example": 12
                        },
                        "total_requests_24h": {
                          "type": "integer",
                          "format": "int64",
                          "description": "Total number of requests captured across all gates in the last 24 hours.",
                          "example": 3480
                        },
                        "total_diff_rate": {
                          "type": "number",
                          "format": "double",
                          "description": "Fraction of requests in the last 24 hours that produced a diff (0.0–1.0).",
                          "example": 0.042
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed because the bearer token was missing or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "title": "Problem",
                  "description": "An error response following RFC 7807 (Problem Details for HTTP APIs). The\n`status` field always matches the HTTP response status, and `type` is a\nrelative URI such as `/errors/not-found` or `/errors/invalid-request-body`.\n",
                  "additionalProperties": false,
                  "required": [
                    "type",
                    "title",
                    "status"
                  ],
                  "properties": {
                    "type": {
                      "type": "string",
                      "description": "A relative URI reference identifying the error type.",
                      "example": "/errors/not-found"
                    },
                    "title": {
                      "type": "string",
                      "description": "A short, human-readable summary of the error type.",
                      "example": "Not Found"
                    },
                    "status": {
                      "type": "integer",
                      "description": "The HTTP status code, matching the response status.",
                      "example": 404
                    },
                    "detail": {
                      "type": "string",
                      "description": "A human-readable explanation specific to this occurrence.",
                      "example": "no gate exists with the given id"
                    },
                    "instance": {
                      "type": "string",
                      "description": "A relative URI reference identifying the specific request. Populated for\n4xx errors only.\n",
                      "example": "/gates/9b2e6f0e-6b7a-4c1d-8f3a-2a1b0c9d8e7f"
                    }
                  },
                  "example": {
                    "type": "/errors/not-found",
                    "title": "Not Found",
                    "status": 404,
                    "detail": "no gate exists with the given id",
                    "instance": "/gates/9b2e6f0e-6b7a-4c1d-8f3a-2a1b0c9d8e7f"
                  }
                }
              }
            }
          },
          "429": {
            "description": "The client has exceeded the rate limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          },
          "500": {
            "description": "An unexpected error occurred while processing the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          }
        }
      }
    },
    "/gates": {
      "get": {
        "operationId": "listGates",
        "summary": "List gates",
        "description": "Returns a paginated list of gates with optional filtering and sorting.",
        "tags": [
          "Gates"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of items to return. Defaults to 50; capped at 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            },
            "example": 50
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of items to skip before the returned page. Defaults to 0.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "example": 0
          },
          {
            "name": "name",
            "in": "query",
            "required": false,
            "description": "Filter gates whose name contains this substring.",
            "schema": {
              "type": "string"
            },
            "example": "users"
          },
          {
            "name": "live_url",
            "in": "query",
            "required": false,
            "description": "Filter gates whose live URL contains this substring.",
            "schema": {
              "type": "string"
            },
            "example": "example.com"
          },
          {
            "name": "shadow_url",
            "in": "query",
            "required": false,
            "description": "Filter gates whose shadow URL contains this substring.",
            "schema": {
              "type": "string"
            },
            "example": "shadow.example.com"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Field to sort gates by.",
            "schema": {
              "type": "string",
              "enum": [
                "id",
                "name",
                "live_url",
                "shadow_url",
                "created_at"
              ],
              "default": "created_at"
            },
            "example": "created_at"
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "Sort direction.",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "desc"
            },
            "example": "desc"
          }
        ],
        "responses": {
          "200": {
            "description": "A paginated list of gates.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "title": "GateListResponse",
                  "description": "Paginated envelope wrapping a list of Gate objects.",
                  "additionalProperties": false,
                  "required": [
                    "data",
                    "pagination"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "description": "The gates in this page.",
                      "items": {
                        "$ref": "#/paths/~1gates/post/responses/201/content/application~1json/schema/properties/data"
                      }
                    },
                    "pagination": {
                      "type": "object",
                      "title": "PaginationMeta",
                      "description": "Pagination metadata for list responses.",
                      "additionalProperties": false,
                      "required": [
                        "limit",
                        "offset",
                        "total",
                        "has_more"
                      ],
                      "properties": {
                        "limit": {
                          "type": "integer",
                          "description": "Maximum number of items returned in this page.",
                          "example": 50
                        },
                        "offset": {
                          "type": "integer",
                          "description": "Number of items skipped before this page.",
                          "example": 0
                        },
                        "total": {
                          "type": "integer",
                          "format": "int64",
                          "description": "Total number of items matching the query.",
                          "example": 128
                        },
                        "has_more": {
                          "type": "boolean",
                          "description": "Whether more items are available beyond this page.",
                          "example": true
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request was malformed (invalid query parameter).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed because the bearer token was missing or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          },
          "429": {
            "description": "The client has exceeded the rate limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          },
          "500": {
            "description": "An unexpected error occurred while processing the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createGate",
        "summary": "Create a gate",
        "description": "Creates a new gate with live and shadow URLs.",
        "tags": [
          "Gates"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "title": "CreateGateRequest",
                "description": "Request body for creating a gate.",
                "additionalProperties": false,
                "required": [
                  "name",
                  "live_url",
                  "shadow_url"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Human-readable gate name (must be unique across gates).",
                    "example": "users-service"
                  },
                  "live_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Base URL of the live (production) service.",
                    "example": "https://live.example.com"
                  },
                  "shadow_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Base URL of the shadow (candidate) service.",
                    "example": "https://shadow.example.com"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created gate.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "title": "GateResponse",
                  "description": "Envelope wrapping a single Gate object.",
                  "additionalProperties": false,
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "title": "Gate",
                      "description": "A traffic-testing gate with live and shadow URLs.",
                      "additionalProperties": false,
                      "required": [
                        "id",
                        "name",
                        "live_url",
                        "shadow_url",
                        "diff_config",
                        "redacted_fields",
                        "retention",
                        "created_at",
                        "stats"
                      ],
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid",
                          "description": "Unique gate identifier.",
                          "example": "9b2e6f0e-6b7a-4c1d-8f3a-2a1b0c9d8e7f"
                        },
                        "name": {
                          "type": "string",
                          "description": "Human-readable gate name (unique across gates).",
                          "example": "users-service"
                        },
                        "live_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "Base URL of the live (production) service.",
                          "example": "https://live.example.com"
                        },
                        "shadow_url": {
                          "type": "string",
                          "format": "uri",
                          "description": "Base URL of the shadow (candidate) service.",
                          "example": "https://shadow.example.com"
                        },
                        "diff_config": {
                          "$ref": "#/paths/~1gates~1%7Bgate_id%7D/patch/requestBody/content/application~1json/schema/properties/diff_config"
                        },
                        "redacted_fields": {
                          "type": "array",
                          "description": "JSON paths whose values are redacted before storage.",
                          "items": {
                            "type": "string"
                          },
                          "example": [
                            "/password",
                            "/token"
                          ]
                        },
                        "retention": {
                          "type": "string",
                          "description": "Per-gate retention as a Go duration string (e.g. `168h`). An empty string\nmeans the gate uses the global retention floor.\n",
                          "example": "168h"
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time",
                          "description": "When the gate was created (RFC 3339).",
                          "example": "2024-04-20T09:00:00Z"
                        },
                        "stats": {
                          "type": "object",
                          "title": "GateStats",
                          "description": "Computed statistics for a single gate.",
                          "additionalProperties": false,
                          "required": [
                            "request_count_24h",
                            "diff_count_24h",
                            "diff_rate",
                            "last_active"
                          ],
                          "properties": {
                            "request_count_24h": {
                              "type": "integer",
                              "format": "int64",
                              "description": "Number of requests captured for this gate in the last 24 hours.",
                              "example": 240
                            },
                            "diff_count_24h": {
                              "type": "integer",
                              "format": "int64",
                              "description": "Number of requests that produced a diff in the last 24 hours.",
                              "example": 6
                            },
                            "diff_rate": {
                              "type": "number",
                              "format": "double",
                              "description": "Fraction of requests that produced a diff in the last 24 hours (0.0–1.0).",
                              "example": 0.025
                            },
                            "last_active": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "format": "date-time",
                              "description": "Timestamp of the most recent captured request, or null if none.",
                              "example": "2024-05-01T12:34:56Z"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request was malformed (invalid or missing body field).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed because the bearer token was missing or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          },
          "409": {
            "description": "A gate with the same name or live/shadow URL pair already exists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          },
          "429": {
            "description": "The client has exceeded the rate limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          },
          "500": {
            "description": "An unexpected error occurred while processing the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          }
        }
      }
    },
    "/gates/{gate_id}": {
      "get": {
        "operationId": "getGate",
        "summary": "Get a gate",
        "description": "Returns a single gate by its identifier.",
        "tags": [
          "Gates"
        ],
        "parameters": [
          {
            "name": "gate_id",
            "in": "path",
            "required": true,
            "description": "The unique identifier of the gate.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "9b2e6f0e-6b7a-4c1d-8f3a-2a1b0c9d8e7f"
          }
        ],
        "responses": {
          "200": {
            "description": "The requested gate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1gates/post/responses/201/content/application~1json/schema"
                }
              }
            }
          },
          "400": {
            "description": "The request was malformed (invalid gate id).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed because the bearer token was missing or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          },
          "404": {
            "description": "No gate exists with the given id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          },
          "429": {
            "description": "The client has exceeded the rate limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          },
          "500": {
            "description": "An unexpected error occurred while processing the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateGate",
        "summary": "Update a gate",
        "description": "Updates a gate. All fields are optional; omitted fields are left unchanged.\n",
        "tags": [
          "Gates"
        ],
        "parameters": [
          {
            "$ref": "#/paths/~1gates~1%7Bgate_id%7D/get/parameters/0"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "title": "UpdateGateRequest",
                "description": "Request body for updating a gate. All fields are optional; omitted fields are\nleft unchanged.\n",
                "additionalProperties": false,
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "New gate name.",
                    "example": "users-service-v2"
                  },
                  "diff_config": {
                    "type": "object",
                    "title": "DiffConfig",
                    "description": "Per-gate diff computation settings.",
                    "additionalProperties": false,
                    "required": [
                      "ignored_fields",
                      "included_fields",
                      "float_tolerance",
                      "sort_arrays"
                    ],
                    "properties": {
                      "ignored_fields": {
                        "type": "array",
                        "description": "JSON paths excluded from comparison.",
                        "items": {
                          "type": "string"
                        },
                        "example": [
                          "/timestamp",
                          "/request_id"
                        ]
                      },
                      "included_fields": {
                        "type": "array",
                        "description": "JSON paths to compare exclusively. When non-empty, only these paths are\ncompared and everything else is ignored.\n",
                        "items": {
                          "type": "string"
                        },
                        "example": []
                      },
                      "float_tolerance": {
                        "type": "number",
                        "format": "double",
                        "description": "Absolute tolerance applied when comparing floating-point numbers.",
                        "example": 0.0001
                      },
                      "sort_arrays": {
                        "type": "boolean",
                        "description": "Whether arrays are sorted before comparison so element order is ignored.",
                        "example": false
                      }
                    }
                  },
                  "redacted_fields": {
                    "type": "array",
                    "description": "Replacement list of JSON paths to redact before storage.",
                    "items": {
                      "type": "string"
                    },
                    "example": [
                      "/password"
                    ]
                  },
                  "retention": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Per-gate retention as a Go duration string. This field is tri-state:\nomit it to leave the value unchanged; send `null` or an empty string to\nreset to the global retention floor; send a duration (e.g. `168h`) to set\na custom value.\n",
                    "example": "168h"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated gate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1gates/post/responses/201/content/application~1json/schema"
                }
              }
            }
          },
          "400": {
            "description": "The request was malformed (invalid body or gate id).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed because the bearer token was missing or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          },
          "404": {
            "description": "No gate exists with the given id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          },
          "409": {
            "description": "The update conflicts with an existing gate (duplicate name or URL pair).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          },
          "429": {
            "description": "The client has exceeded the rate limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          },
          "500": {
            "description": "An unexpected error occurred while processing the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteGate",
        "summary": "Delete a gate",
        "description": "Deletes a gate and all of its captured requests.",
        "tags": [
          "Gates"
        ],
        "parameters": [
          {
            "$ref": "#/paths/~1gates~1%7Bgate_id%7D/get/parameters/0"
          }
        ],
        "responses": {
          "204": {
            "description": "The gate was deleted."
          },
          "400": {
            "description": "The request was malformed (invalid gate id).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed because the bearer token was missing or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          },
          "404": {
            "description": "No gate exists with the given id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          },
          "429": {
            "description": "The client has exceeded the rate limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          },
          "500": {
            "description": "An unexpected error occurred while processing the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          }
        }
      }
    },
    "/gates/{gate_id}/requests": {
      "get": {
        "operationId": "listRequests",
        "summary": "List requests for a gate",
        "description": "Returns a paginated list of requests captured for a gate, with optional filtering and sorting.",
        "tags": [
          "Requests"
        ],
        "parameters": [
          {
            "$ref": "#/paths/~1gates~1%7Bgate_id%7D/get/parameters/0"
          },
          {
            "$ref": "#/paths/~1gates/get/parameters/0"
          },
          {
            "$ref": "#/paths/~1gates/get/parameters/1"
          },
          {
            "name": "method",
            "in": "query",
            "required": false,
            "description": "Filter by HTTP method. Accepts a comma-separated list to match any of the\nlisted methods (e.g. `GET,POST`).\n",
            "schema": {
              "type": "string"
            },
            "example": "GET,POST"
          },
          {
            "name": "path",
            "in": "query",
            "required": false,
            "description": "Filter requests whose path contains this substring.",
            "schema": {
              "type": "string"
            },
            "example": "/api/users"
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Only include requests created at or after this timestamp (RFC 3339).",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "example": "2024-05-01T00:00:00Z"
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Only include requests created at or before this timestamp (RFC 3339).",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "example": "2024-05-02T00:00:00Z"
          },
          {
            "name": "has_diff",
            "in": "query",
            "required": false,
            "description": "Filter by whether a request produced a diff.",
            "schema": {
              "type": "boolean"
            },
            "example": true
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Field to sort requests by.",
            "schema": {
              "type": "string",
              "enum": [
                "created_at",
                "method",
                "path"
              ],
              "default": "created_at"
            },
            "example": "created_at"
          },
          {
            "$ref": "#/paths/~1gates/get/parameters/6"
          }
        ],
        "responses": {
          "200": {
            "description": "A paginated list of request summaries.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "title": "RequestListResponse",
                  "description": "Paginated envelope wrapping a list of Request summary objects.",
                  "additionalProperties": false,
                  "required": [
                    "data",
                    "pagination"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "description": "The requests in this page.",
                      "items": {
                        "$ref": "#/paths/~1gates~1%7Bgate_id%7D~1requests/post/responses/201/content/application~1json/schema/properties/data"
                      }
                    },
                    "pagination": {
                      "$ref": "#/paths/~1gates/get/responses/200/content/application~1json/schema/properties/pagination"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request was malformed (invalid query or path parameter).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed because the bearer token was missing or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          },
          "404": {
            "description": "No gate exists with the given id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          },
          "429": {
            "description": "The client has exceeded the rate limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          },
          "500": {
            "description": "An unexpected error occurred while processing the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createRequest",
        "summary": "Create a request",
        "description": "Submits a captured request with its live and shadow responses. If a diff is\nnot supplied, the API computes it server-side.\n",
        "tags": [
          "Requests"
        ],
        "parameters": [
          {
            "$ref": "#/paths/~1gates~1%7Bgate_id%7D/get/parameters/0"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "title": "CreateRequestPayload",
                "description": "Payload for creating a request with its live and shadow responses and an\noptional pre-computed diff. Sent from proxies to the API.\n",
                "additionalProperties": false,
                "required": [
                  "method",
                  "path",
                  "headers",
                  "body",
                  "created_at",
                  "live_response",
                  "shadow_response"
                ],
                "properties": {
                  "id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Optional request identifier. When omitted, the API uses the `X-Request-ID`\nheader if present, otherwise it generates one.\n",
                    "example": "3f1c2d4e-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
                  },
                  "method": {
                    "type": "string",
                    "description": "HTTP method of the captured request.",
                    "example": "GET"
                  },
                  "path": {
                    "type": "string",
                    "description": "Request path.",
                    "example": "/api/users/123"
                  },
                  "raw_query": {
                    "type": "string",
                    "description": "Raw query string, omitted when empty.",
                    "example": "page=2&limit=10"
                  },
                  "headers": {
                    "type": "object",
                    "description": "Request headers as a map of header name to a list of values.",
                    "additionalProperties": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "example": {
                      "Accept": [
                        "application/json"
                      ]
                    }
                  },
                  "body": {
                    "type": "string",
                    "description": "Base64-encoded request body.",
                    "example": ""
                  },
                  "created_at": {
                    "type": "string",
                    "format": "date-time",
                    "description": "When the request was captured (RFC 3339).",
                    "example": "2024-05-01T12:34:56Z"
                  },
                  "live_response": {
                    "type": "object",
                    "title": "ResponsePayload",
                    "description": "A single HTTP response submitted by a proxy.",
                    "additionalProperties": false,
                    "required": [
                      "status_code",
                      "headers",
                      "body",
                      "latency_ms",
                      "created_at"
                    ],
                    "properties": {
                      "id": {
                        "type": "string",
                        "format": "uuid",
                        "description": "Optional response identifier; generated by the API when omitted.",
                        "example": "7a8b9c0d-1e2f-4a3b-9c8d-7e6f5a4b3c2d"
                      },
                      "status_code": {
                        "type": "integer",
                        "description": "HTTP status code of the response.",
                        "example": 200
                      },
                      "headers": {
                        "type": "object",
                        "description": "Response headers as a map of header name to a list of values.",
                        "additionalProperties": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "example": {
                          "Content-Type": [
                            "application/json"
                          ]
                        }
                      },
                      "body": {
                        "type": "string",
                        "description": "Base64-encoded response body.",
                        "example": "eyJvayI6dHJ1ZX0="
                      },
                      "latency_ms": {
                        "type": "integer",
                        "format": "int64",
                        "description": "Response time in milliseconds.",
                        "example": 42
                      },
                      "created_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "When the response was captured (RFC 3339).",
                        "example": "2024-05-01T12:34:56Z"
                      }
                    }
                  },
                  "shadow_response": {
                    "$ref": "#/paths/~1gates~1%7Bgate_id%7D~1requests/post/requestBody/content/application~1json/schema/properties/live_response"
                  },
                  "diff": {
                    "type": "object",
                    "title": "DiffPayload",
                    "description": "The computed difference between two responses.",
                    "additionalProperties": false,
                    "required": [
                      "content"
                    ],
                    "properties": {
                      "content": {
                        "type": "array",
                        "description": "RFC 6902 JSON Patch operations.",
                        "items": {
                          "type": "object",
                          "title": "PatchOp",
                          "description": "A single RFC 6902 JSON Patch operation describing one difference.",
                          "additionalProperties": false,
                          "required": [
                            "op",
                            "path"
                          ],
                          "properties": {
                            "op": {
                              "type": "string",
                              "description": "The JSON Patch operation.",
                              "enum": [
                                "add",
                                "remove",
                                "replace"
                              ],
                              "example": "replace"
                            },
                            "path": {
                              "type": "string",
                              "description": "JSON Pointer (RFC 6901) to the location that differs.",
                              "example": "/data/name"
                            },
                            "value": {
                              "description": "The value associated with the operation. Present for `add` and `replace`\noperations; omitted for `remove`. May be any JSON type.\n",
                              "example": "new-value"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created request summary.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "title": "RequestSummaryResponse",
                  "description": "Envelope wrapping a single Request summary object.",
                  "additionalProperties": false,
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "title": "Request",
                      "description": "Summary of a captured request, used in listings.",
                      "additionalProperties": false,
                      "required": [
                        "id",
                        "method",
                        "path",
                        "created_at",
                        "live_response",
                        "shadow_response",
                        "has_diff"
                      ],
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid",
                          "description": "Unique request identifier.",
                          "example": "3f1c2d4e-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
                        },
                        "method": {
                          "type": "string",
                          "description": "HTTP method of the captured request.",
                          "example": "GET"
                        },
                        "path": {
                          "type": "string",
                          "description": "Request path.",
                          "example": "/api/users/123"
                        },
                        "raw_query": {
                          "type": "string",
                          "description": "Raw query string, omitted when empty.",
                          "example": "page=2&limit=10"
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time",
                          "description": "When the request was captured (RFC 3339).",
                          "example": "2024-05-01T12:34:56Z"
                        },
                        "live_response": {
                          "type": "object",
                          "title": "ResponseSummary",
                          "description": "Lightweight response summary used in request listings.",
                          "additionalProperties": false,
                          "required": [
                            "status_code",
                            "latency_ms"
                          ],
                          "properties": {
                            "status_code": {
                              "type": "integer",
                              "description": "HTTP status code of the response.",
                              "example": 200
                            },
                            "latency_ms": {
                              "type": "integer",
                              "format": "int64",
                              "description": "Response time in milliseconds.",
                              "example": 42
                            }
                          }
                        },
                        "shadow_response": {
                          "$ref": "#/paths/~1gates~1%7Bgate_id%7D~1requests/post/responses/201/content/application~1json/schema/properties/data/properties/live_response"
                        },
                        "has_diff": {
                          "type": "boolean",
                          "description": "Whether a diff was detected between the live and shadow responses.",
                          "example": false
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request was malformed (invalid or missing body field).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed because the bearer token was missing or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          },
          "404": {
            "description": "No gate exists with the given id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          },
          "429": {
            "description": "The client has exceeded the rate limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          },
          "500": {
            "description": "An unexpected error occurred while processing the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          }
        }
      }
    },
    "/gates/{gate_id}/requests/{request_id}": {
      "get": {
        "operationId": "getRequest",
        "summary": "Get a request",
        "description": "Returns a single captured request with full responses and diff.",
        "tags": [
          "Requests"
        ],
        "parameters": [
          {
            "$ref": "#/paths/~1gates~1%7Bgate_id%7D/get/parameters/0"
          },
          {
            "name": "request_id",
            "in": "path",
            "required": true,
            "description": "The unique identifier of the request.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "3f1c2d4e-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
          }
        ],
        "responses": {
          "200": {
            "description": "The requested request detail.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "title": "RequestDetailResponse",
                  "description": "Envelope wrapping a single RequestDetail object.",
                  "additionalProperties": false,
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "title": "RequestDetail",
                      "description": "A complete request with full responses and diff.",
                      "additionalProperties": false,
                      "required": [
                        "id",
                        "method",
                        "path",
                        "headers",
                        "body",
                        "created_at",
                        "live_response",
                        "shadow_response",
                        "diff"
                      ],
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid",
                          "description": "Unique request identifier.",
                          "example": "3f1c2d4e-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
                        },
                        "method": {
                          "type": "string",
                          "description": "HTTP method of the captured request.",
                          "example": "GET"
                        },
                        "path": {
                          "type": "string",
                          "description": "Request path.",
                          "example": "/api/users/123"
                        },
                        "raw_query": {
                          "type": "string",
                          "description": "Raw query string, omitted when empty.",
                          "example": "page=2&limit=10"
                        },
                        "headers": {
                          "type": "object",
                          "description": "Request headers as a map of header name to a list of values.",
                          "additionalProperties": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "example": {
                            "Accept": [
                              "application/json"
                            ]
                          }
                        },
                        "body": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Base64-encoded request body, or null when absent.",
                          "example": null
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time",
                          "description": "When the request was captured (RFC 3339).",
                          "example": "2024-05-01T12:34:56Z"
                        },
                        "live_response": {
                          "type": "object",
                          "title": "ResponseDetail",
                          "description": "A response with full details, used in the request detail view.",
                          "additionalProperties": false,
                          "required": [
                            "id",
                            "status_code",
                            "headers",
                            "body",
                            "latency_ms",
                            "created_at"
                          ],
                          "properties": {
                            "id": {
                              "type": "string",
                              "format": "uuid",
                              "description": "Unique response identifier.",
                              "example": "7a8b9c0d-1e2f-4a3b-9c8d-7e6f5a4b3c2d"
                            },
                            "status_code": {
                              "type": "integer",
                              "description": "HTTP status code of the response.",
                              "example": 200
                            },
                            "headers": {
                              "type": "object",
                              "description": "Response headers as a map of header name to a list of values.",
                              "additionalProperties": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              },
                              "example": {
                                "Content-Type": [
                                  "application/json"
                                ]
                              }
                            },
                            "body": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "Base64-encoded response body, or null when absent.",
                              "example": "eyJvayI6dHJ1ZX0="
                            },
                            "latency_ms": {
                              "type": "integer",
                              "format": "int64",
                              "description": "Response time in milliseconds.",
                              "example": 42
                            },
                            "created_at": {
                              "type": "string",
                              "format": "date-time",
                              "description": "When the response was captured (RFC 3339).",
                              "example": "2024-05-01T12:34:56Z"
                            }
                          }
                        },
                        "shadow_response": {
                          "$ref": "#/paths/~1gates~1%7Bgate_id%7D~1requests~1%7Brequest_id%7D/get/responses/200/content/application~1json/schema/properties/data/properties/live_response"
                        },
                        "diff": {
                          "type": "object",
                          "title": "DiffDetail",
                          "description": "Diff content and the diff config snapshot used to compute it.",
                          "additionalProperties": false,
                          "required": [
                            "content",
                            "config"
                          ],
                          "properties": {
                            "content": {
                              "type": "array",
                              "description": "RFC 6902 JSON Patch operations describing the differences.",
                              "items": {
                                "$ref": "#/paths/~1gates~1%7Bgate_id%7D~1requests/post/requestBody/content/application~1json/schema/properties/diff/properties/content/items"
                              }
                            },
                            "config": {
                              "$ref": "#/paths/~1gates~1%7Bgate_id%7D/patch/requestBody/content/application~1json/schema/properties/diff_config"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request was malformed (invalid gate or request id).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed because the bearer token was missing or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          },
          "404": {
            "description": "No request exists with the given id for this gate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          },
          "429": {
            "description": "The client has exceeded the rate limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          },
          "500": {
            "description": "An unexpected error occurred while processing the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/paths/~1stats/get/responses/401/content/application~1json/schema"
                }
              }
            }
          }
        }
      }
    },
    "/health/live": {
      "get": {
        "operationId": "getLiveness",
        "summary": "Liveness probe",
        "description": "Always responds with `200 OK` while the process is running. Intended for\nKubernetes liveness probes. Unauthenticated; bypasses the API middleware.\n",
        "tags": [
          "Health"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The process is alive.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "title": "HealthStatus",
                  "description": "A plain-text health probe body. `OK` when healthy; otherwise a short\ndiagnostic message.\n",
                  "example": "OK"
                }
              }
            }
          }
        }
      }
    },
    "/health/ready": {
      "get": {
        "operationId": "getReadiness",
        "summary": "Readiness probe",
        "description": "Checks database connectivity within a 1-second timeout. Returns `200 OK`\nwhen reachable, or `503` with a diagnostic message otherwise. Intended for\nKubernetes readiness and startup probes. Unauthenticated; bypasses the API\nmiddleware.\n",
        "tags": [
          "Health"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The service is ready to accept traffic.",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/paths/~1health~1live/get/responses/200/content/text~1plain/schema"
                }
              }
            }
          },
          "503": {
            "description": "The service is not ready (database unreachable).",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/paths/~1health~1live/get/responses/200/content/text~1plain/schema"
                }
              }
            }
          }
        }
      }
    },
    "/metrics": {
      "get": {
        "operationId": "getMetrics",
        "summary": "Prometheus metrics",
        "description": "Exposes metrics in the Prometheus text exposition format. Only mounted when\nmetrics are enabled. Unauthenticated; bypasses the API middleware.\n",
        "tags": [
          "Metrics"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Metrics in the Prometheus text exposition format.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "title": "PrometheusMetrics",
                  "description": "Metrics in the Prometheus text exposition format.",
                  "example": "# HELP http_server_request_duration_seconds Duration of HTTP server requests.\n# TYPE http_server_request_duration_seconds histogram\nhttp_server_request_duration_seconds_count{http_route=\"/gates\"} 42\n"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key supplied as `Authorization: Bearer <your-api-key>`."
      }
    }
  }
}
