{
  "openapi": "3.1.0",
  "info": {
    "title": "Skontro Public API",
    "version": "1.0.0",
    "summary": "Measured all-in crypto trading cost + sourced exchange reliability — no key required.",
    "description": "The same numbers that power skontro.com, as a public read-only REST API. No API key, no account. Every live response carries a `disclaimer` string and per-venue data-age fields (seconds); missing data is shown as absent, never guessed. Please cite \"Skontro (skontro.com)\" and include the data age when quoting figures — these are live measurements, not static tables.\n\nNot investment advice. Skontro is not a CASP under MiCAR — we show market data, you decide. Ranking is always by net amount received and is never influenced by affiliate payout.\n\nRate limits: 60 requests/min per IP on each endpoint (20/min on /api/truecost/swap, a real DEX round-trip), under a global 120/min per-IP ceiling. No SLA — best effort. Breaking changes are announced at least 14 days ahead on /developers and /status. There is an MCP server over the same engine at https://api.skontro.com/api/mcp (see /developers).",
    "contact": {
      "name": "Skontro",
      "url": "https://skontro.com/developers"
    }
  },
  "servers": [
    {
      "url": "https://api.skontro.com",
      "description": "Skontro public API"
    }
  ],
  "tags": [
    {
      "name": "true-cost",
      "description": "Measured all-in trading cost."
    },
    {
      "name": "reliability",
      "description": "Sourced Reliability Score per exchange."
    },
    {
      "name": "ops",
      "description": "Feed freshness and service health."
    }
  ],
  "paths": {
    "/api/truecost": {
      "get": {
        "tags": [
          "true-cost"
        ],
        "summary": "Ranked all-in cost of one trade across all covered exchanges.",
        "description": "Published fee + hidden spread (from walking the live order book for your exact size), optionally + withdrawal fee, ranked by net units received (buying: coins; selling: quote currency). Ranking is by net amount only — never fee headline, never affiliate payout; in quote=USD responses USD and USDT books rank as separate groups (a USDT price assumes the peg). Every live response carries a `disclaimer` string and per-venue data-age fields (seconds); missing data is shown as absent, never guessed.",
        "parameters": [
          {
            "name": "asset",
            "in": "query",
            "required": true,
            "description": "Ticker, e.g. BTC. Supported assets are enumerated by /api/truecost/venues.",
            "schema": {
              "type": "string",
              "example": "BTC"
            }
          },
          {
            "name": "amount",
            "in": "query",
            "required": true,
            "description": "Trade size — a budget in the quote currency when side=buy (EUR, or dollars when quote=USD), asset units when side=sell.",
            "schema": {
              "type": "number",
              "minimum": 0,
              "exclusiveMinimum": true,
              "example": 1000
            }
          },
          {
            "name": "side",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "buy",
                "sell"
              ],
              "default": "buy"
            }
          },
          {
            "name": "quote",
            "in": "query",
            "required": false,
            "description": "Selects WHICH order books are measured — never a display conversion. EUR walks each venue's ASSET/EUR pair (the founding behaviour). USD walks each venue's own measured dollar rail: a genuine fiat-USD book where the venue has one, its deep USDT book where it does not — each row states its `denomination` (EUR/USD/USDT) and dollar rows carry their money in *Quote fields (the *Eur fields are absent there). USD and USDT rows are ranked as SEPARATE groups (fiat group first) because a USDT price assumes the peg; venues without a public dollar book or without a rail-verified fee schedule appear in `omitted` with a stated reason. Any other value returns 400, naming what IS supported, rather than a silent empty `venues` array (an unmeasured currency is not the same thing as \"no venue is cheap enough\").",
            "schema": {
              "type": "string",
              "default": "EUR",
              "enum": [
                "EUR",
                "USD"
              ],
              "example": "EUR"
            }
          },
          {
            "name": "includeWithdrawal",
            "in": "query",
            "required": false,
            "description": "Include the curated native-chain withdrawal fee in each total. Venues with no published fixed fee keep their total withdrawal-free and are flagged.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Ranked comparison for the requested trade.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TrueCostResponse"
                }
              }
            }
          },
          "400": {
            "description": "Unsupported asset, or an unsupported quote currency (EUR and USD are measured — see /api/truecost/venues).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Live data backend temporarily unavailable (Retry-After header set).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/truecost/venues": {
      "get": {
        "tags": [
          "true-cost"
        ],
        "summary": "Covered exchanges with fee schedule + freshness metadata.",
        "description": "The list of covered venues, each with its base-tier maker/taker fee (bps), whether it has a public order book, whether the venue link is monetized, the fee source and the last fee-verification date. Enumerates the supported venue set (no hardcoded count in copy — this endpoint is the source of truth).",
        "responses": {
          "200": {
            "description": "Array of venue metadata rows.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/VenueRow"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/truecost/withdrawal-fees": {
      "get": {
        "tags": [
          "true-cost"
        ],
        "summary": "Curated native-chain BTC/ETH withdrawal fees per exchange, USD- and EUR-valued.",
        "description": "Curated withdrawal fees for BTC and ETH on their native chains, each with its official source URL and USD + EUR value at the cached reference prices (both are native CoinGecko quotes — never one converted from the other). Venues without a published fixed fee return null rows — absent means unknown, never zero.",
        "responses": {
          "200": {
            "description": "Per-venue withdrawal-fee map plus the reference prices used.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WithdrawalFeesResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/truecost/index": {
      "get": {
        "tags": [
          "true-cost"
        ],
        "summary": "Monthly True-Cost-Index — measured cost per €1,000 trade per exchange.",
        "description": "The monthly aggregate (average fee, hidden spread and all-in cost per €1,000 trade per exchange) from hourly order-book measurements. `report` is null until a month has accrued enough data — the placeholder shape still returns `availableMonths` and `generatedAt` (an honest \"first report soon\" state, never a fabricated table).",
        "parameters": [
          {
            "name": "month",
            "in": "query",
            "required": false,
            "description": "YYYY-MM. Omitted → the latest month that has rankable data.",
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}$",
              "example": "2026-06"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The requested (or latest) month. `report` may be null with a populated `availableMonths` when no full month has accrued yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IndexResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/truecost/swap": {
      "get": {
        "tags": [
          "true-cost"
        ],
        "summary": "DEX swap route (LiFi-aggregated) in the same cost shape as the CEX table.",
        "description": "One LiFi-aggregated route per pair (ETH, WBTC, USDC, USDT, DAI on Ethereum), mapped onto the same cost model and priced in BOTH USD and EUR from that single route — each currency from its own native reference price, never a converted one. The response therefore carries up to two `venues` rows for ONE route, told apart by `denomination`; a currency without a native reference is omitted rather than estimated. When no route or no reference at all is available it returns an honest 503 rather than a guessed number — a documented A1.9 behaviour, not an error to retry blindly. Tighter 20/min limit: a cache miss is a real LiFi round-trip.",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "ETH",
                "WBTC",
                "USDC",
                "USDT",
                "DAI"
              ],
              "example": "ETH"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "ETH",
                "WBTC",
                "USDC",
                "USDT",
                "DAI"
              ],
              "example": "USDC"
            }
          },
          {
            "name": "amount",
            "in": "query",
            "required": true,
            "schema": {
              "type": "number",
              "minimum": 0,
              "exclusiveMinimum": true,
              "example": 1
            }
          },
          {
            "name": "chain",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "default": "ETHEREUM",
              "example": "ETHEREUM"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The DEX route quote (one route, one row per priced currency).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SwapResponse"
                }
              }
            }
          },
          "400": {
            "description": "Unsupported token, or from === to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "No DEX route or reference price available right now — the honest \"we show nothing rather than a guess\" state (A1.9). The body is the Error shape, NOT a zeroed quote.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/trust": {
      "get": {
        "tags": [
          "reliability"
        ],
        "summary": "Sourced Reliability Score per exchange (0–100, six weighted components).",
        "description": "The Reliability Score for every covered exchange — six weighted components (withdrawal reliability, proof of reserves, regulatory standing, security history, support, maturity), every negative datapoint dated and sourced. Detail and history per exchange: GET /api/trust/{exchange} and GET /api/trust/{exchange}/history (404-safe for unknown slugs). Describes recorded facts — not a recommendation to use or avoid any venue.",
        "responses": {
          "200": {
            "description": "Methodology version, generation time, and all scored exchanges.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TrustListResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/status": {
      "get": {
        "tags": [
          "ops"
        ],
        "summary": "Per-venue feed freshness + backend health, live.",
        "description": "The public data-status snapshot: per-venue order-book age for the BTC/ETH anchors, active accuracy suppressions, estimated venues, worker job heartbeats and the overall backend state. This is where our own feed degradation is public.",
        "responses": {
          "200": {
            "description": "The live status snapshot.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StatusResponse"
                }
              }
            }
          }
        }
      }
    },
    "/health": {
      "get": {
        "tags": [
          "ops"
        ],
        "summary": "Liveness probe — db + redis reachability.",
        "description": "A minimal health probe: overall status plus db and redis reachability.",
        "responses": {
          "200": {
            "description": "Health snapshot (status ok/degraded).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthResponse"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Error": {
        "type": "object",
        "description": "NestJS default error envelope (also the honest 503/400 body).",
        "properties": {
          "statusCode": {
            "type": "integer",
            "example": 503
          },
          "message": {
            "type": "string",
            "example": "Live data backend is unavailable — the comparison resumes shortly."
          },
          "error": {
            "type": "string",
            "example": "Service Unavailable"
          }
        }
      },
      "TrueCostResponse": {
        "type": "object",
        "required": [
          "query",
          "refPriceEur",
          "eurUsdRate",
          "generatedAt",
          "venues",
          "ageSpreadSeconds",
          "ageWarning",
          "disclaimer"
        ],
        "properties": {
          "query": {
            "type": "object",
            "properties": {
              "asset": {
                "type": "string"
              },
              "amount": {
                "type": "number"
              },
              "quote": {
                "type": "string"
              },
              "side": {
                "type": "string",
                "enum": [
                  "buy",
                  "sell"
                ]
              },
              "includeWithdrawal": {
                "type": "boolean"
              }
            }
          },
          "refPriceEur": {
            "type": [
              "number",
              "null"
            ],
            "description": "EUR reference price; null on a cold cache."
          },
          "refPriceQuote": {
            "type": [
              "number",
              "null"
            ],
            "description": "quote=USD responses only: the NATIVE dollar reference the fee/spread decomposition was measured against — CoinGecko's own USD quote, never the EUR reference multiplied by a rate. Published so our spread figures can be audited without reconstructing the reference. null on a cold cache; absent on EUR responses, where refPriceEur is this number."
          },
          "eurUsdRate": {
            "type": [
              "number",
              "null"
            ],
            "description": "Display-only USD toggle rate; never a ranking input, and since 2026-08-03 no longer the basis of the dollar reference either (see refPriceQuote)."
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "venues": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TrueCostVenueRow"
            }
          },
          "ageSpreadSeconds": {
            "type": "number",
            "description": "Max age difference across live snapshots (K6)."
          },
          "ageWarning": {
            "type": "boolean"
          },
          "omitted": {
            "type": "array",
            "description": "Venues honestly left out of THIS response, each with a machine reason and a human sentence — present (possibly empty) on both quote modes. quote=USD uses no_dollar_orderbook | fee_schedule_unverified. quote=EUR (Task #24, 2026-08-10) uses the single reason no_eur_orderbook_data — no live EUR row this round; the cause (no book vs. a failed measurement) is deliberately not asserted, since no static per-venue EUR-pair catalogue exists to tell them apart.",
            "items": {
              "type": "object",
              "properties": {
                "venue": {
                  "type": "string"
                },
                "displayName": {
                  "type": "string"
                },
                "reason": {
                  "type": "string",
                  "enum": [
                    "no_dollar_orderbook",
                    "fee_schedule_unverified",
                    "no_eur_orderbook_data"
                  ]
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "disclaimer": {
            "type": "string"
          }
        }
      },
      "TrueCostVenueRow": {
        "type": "object",
        "description": "One ranked venue. Carries its own data age in seconds. Every row states its `denomination` — the currency its money figures were MEASURED in. EUR rows carry the *Eur money fields; quote=USD rows carry the same money in the *Quote fields instead (feeQuote/spreadQuote/totalCostQuote/effectiveUnitPriceQuote — the *Eur fields are ABSENT there: a field name is a claim). In quote=USD responses USD and USDT rows are ranked as separate groups — isCheapest marks the leader of its own rail group.",
        "properties": {
          "venue": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          },
          "denomination": {
            "type": "string",
            "enum": [
              "EUR",
              "USD",
              "USDT"
            ],
            "description": "The measured rail every money figure on this row is denominated in."
          },
          "rail": {
            "type": "string",
            "enum": [
              "USD",
              "USDT"
            ],
            "description": "quote=USD only: which dollar book this row was measured on."
          },
          "effectiveUnits": {
            "type": "number",
            "description": "Net units received — the ranking quantity (K4)."
          },
          "totalCostEur": {
            "type": [
              "number",
              "null"
            ],
            "description": "EUR rows only."
          },
          "feeEur": {
            "type": [
              "number",
              "null"
            ],
            "description": "EUR rows only."
          },
          "spreadEur": {
            "type": [
              "number",
              "null"
            ],
            "description": "EUR rows only."
          },
          "totalCostQuote": {
            "type": [
              "number",
              "null"
            ],
            "description": "quote=USD rows only — denominated in `denomination`."
          },
          "feeQuote": {
            "type": [
              "number",
              "null"
            ],
            "description": "quote=USD rows only — denominated in `denomination`."
          },
          "spreadQuote": {
            "type": [
              "number",
              "null"
            ],
            "description": "quote=USD rows only — denominated in `denomination`."
          },
          "effectiveUnitPriceQuote": {
            "type": [
              "number",
              "null"
            ],
            "description": "quote=USD rows only — denominated in `denomination`."
          },
          "estimated": {
            "type": "boolean"
          },
          "isCheapest": {
            "type": "boolean"
          },
          "affiliateUrl": {
            "type": [
              "string",
              "null"
            ]
          },
          "monetized": {
            "type": "boolean",
            "description": "Disclosure flag — never a ranking input (G9)."
          },
          "dataAgeSeconds": {
            "type": [
              "number",
              "null"
            ]
          },
          "flags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "VenueRow": {
        "type": "object",
        "required": [
          "venue",
          "displayName",
          "hasPublicOrderbook",
          "monetized",
          "makerBps",
          "takerBps"
        ],
        "properties": {
          "venue": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          },
          "hasPublicOrderbook": {
            "type": "boolean"
          },
          "monetized": {
            "type": "boolean"
          },
          "makerBps": {
            "type": "number"
          },
          "takerBps": {
            "type": "number"
          },
          "feeSource": {
            "type": [
              "string",
              "null"
            ]
          },
          "feeUpdatedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "WithdrawalFeesResponse": {
        "type": "object",
        "required": [
          "generatedAt",
          "refPrices",
          "refPricesUsd",
          "venues",
          "note"
        ],
        "properties": {
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "refPrices": {
            "type": "object",
            "additionalProperties": {
              "type": [
                "number",
                "null"
              ]
            }
          },
          "refPricesUsd": {
            "type": "object",
            "additionalProperties": {
              "type": [
                "number",
                "null"
              ]
            },
            "description": "A native USD quote per asset — never derived from `refPrices` via FX."
          },
          "venues": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "venue": {
                  "type": "string"
                },
                "displayName": {
                  "type": "string"
                },
                "fees": {
                  "type": "object",
                  "additionalProperties": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "feeAmount": {
                        "type": "number"
                      },
                      "feeAsset": {
                        "type": "string"
                      },
                      "feeEur": {
                        "type": [
                          "number",
                          "null"
                        ]
                      },
                      "feeUsd": {
                        "type": [
                          "number",
                          "null"
                        ],
                        "description": "feeAmount × refPricesUsd — a native USD valuation, never feeEur converted."
                      },
                      "source": {
                        "type": "string"
                      }
                    }
                  }
                },
                "stablecoins": {
                  "type": "object",
                  "description": "Per-NETWORK rows for USDT and USDC (keys always present, empty array = no curated row). Networks are the issuer’s own native-issuance list, never a wrapped/bridged representation. Same valuation rule as `fees`.",
                  "additionalProperties": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "chain": {
                          "type": "string"
                        },
                        "chainLabel": {
                          "type": "string"
                        },
                        "feeAmount": {
                          "type": "number"
                        },
                        "feeAsset": {
                          "type": "string"
                        },
                        "feeEur": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "feeUsd": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "minAmount": {
                          "type": [
                            "number",
                            "null"
                          ],
                          "description": "Published minimum withdrawal in feeAsset units; null = not curated."
                        },
                        "source": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "note": {
            "type": "string"
          },
          "stablecoinRefPrices": {
            "type": "object",
            "description": "Native EUR/USD quotes behind the stablecoin rows (null when cold — never a $1 par assumption).",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "eur": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "usd": {
                  "type": [
                    "number",
                    "null"
                  ]
                }
              }
            }
          },
          "stablecoinNote": {
            "type": "string"
          }
        }
      },
      "IndexResponse": {
        "type": "object",
        "required": [
          "report",
          "availableMonths",
          "generatedAt"
        ],
        "description": "Monthly True-Cost-Index. `report` is null (placeholder shape) until a month has accrued enough data; `availableMonths` and `generatedAt` are always present.",
        "properties": {
          "report": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "type": "object",
                "description": "Aggregate report for one month (per-venue averages, cheapest/priciest shares).",
                "additionalProperties": true
              }
            ]
          },
          "availableMonths": {
            "type": "array",
            "items": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}$"
            }
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SwapResponse": {
        "type": "object",
        "required": [
          "query",
          "generatedAt",
          "venues"
        ],
        "properties": {
          "query": {
            "type": "object",
            "properties": {
              "from": {
                "type": "string"
              },
              "to": {
                "type": "string"
              },
              "amount": {
                "type": "number"
              },
              "chain": {
                "type": "string"
              }
            }
          },
          "refFromEur": {
            "type": "number",
            "nullable": true
          },
          "refToEur": {
            "type": "number",
            "nullable": true
          },
          "amountInEur": {
            "type": "number",
            "nullable": true
          },
          "refFromUsd": {
            "type": "number",
            "nullable": true
          },
          "refToUsd": {
            "type": "number",
            "nullable": true
          },
          "amountInUsd": {
            "type": "number",
            "nullable": true
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "venues": {
            "type": "array",
            "description": "The SAME LiFi route priced once per currency: up to two rows, each carrying `denomination` (\"EUR\" or \"USD\"). They are two views of one route, never two competing offers — do not rank or sum across them. Following the CEX dollar-rail convention, a USD row does NOT carry dollar figures in `*Eur`-named fields: those are absent and the same numbers travel as `feeQuote`, `spreadQuote`, `networkQuote`, `withdrawalQuote`, `totalCostQuote`, `effectiveUnitPriceQuote`. A currency with no native reference price is simply absent.",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          }
        }
      },
      "TrustListResponse": {
        "type": "object",
        "required": [
          "methodologyVersion",
          "generatedAt",
          "exchanges"
        ],
        "properties": {
          "methodologyVersion": {
            "type": "string"
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "exchanges": {
            "type": "array",
            "items": {
              "type": "object",
              "description": "One exchange: score 0–100 plus its six sourced components.",
              "additionalProperties": true
            }
          }
        }
      },
      "StatusResponse": {
        "type": "object",
        "required": [
          "backend",
          "thresholdSeconds",
          "venues",
          "generatedAt"
        ],
        "properties": {
          "backend": {
            "type": "string",
            "enum": [
              "up",
              "down"
            ]
          },
          "thresholdSeconds": {
            "type": "number"
          },
          "venues": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "venue": {
                  "type": "string"
                },
                "displayName": {
                  "type": "string"
                },
                "ageSeconds": {
                  "type": "object",
                  "additionalProperties": {
                    "type": [
                      "number",
                      "null"
                    ]
                  }
                },
                "state": {
                  "type": "string",
                  "enum": [
                    "fresh",
                    "degraded",
                    "no_data",
                    "unknown"
                  ]
                }
              }
            }
          },
          "suppressions": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "estimatedVenues": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "HealthResponse": {
        "type": "object",
        "required": [
          "status",
          "db",
          "redis"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok",
              "degraded"
            ]
          },
          "db": {
            "type": "string",
            "enum": [
              "up",
              "down"
            ]
          },
          "redis": {
            "type": "string",
            "enum": [
              "up",
              "down"
            ]
          },
          "timestamp": {
            "type": "string",
            "format": "date-time"
          }
        }
      }
    }
  }
}