{
  "openapi": "3.0.0",
  "info": {
    "title": "bankproducts.info API",
    "version": "1.1.0",
    "description": "Structured Swiss + Liechtenstein retail bank product data. 420 banks, 2'790 products, 6'040 fees, 1'820 interest rates. Every fee row carries a `source_page` reference to the original bank PDF. URL contract per ADR-001: BPG-canonical URLs are permanent; slug URLs return HTTP 301 to the canonical BPG URL. List endpoints support cursor pagination via `?limit=N&cursor=...` (default 100, max 500); the response `next_cursor` is the cursor for the next page (or null when this was the last).",
    "license": {
      "name": "CC-BY-4.0",
      "url": "https://creativecommons.org/licenses/by/4.0/"
    },
    "contact": {
      "name": "bankproducts.info",
      "url": "https://bankproducts.info"
    }
  },
  "servers": [
    {
      "url": "https://api.bankproducts.info",
      "description": "Production"
    },
    {
      "url": "http://localhost:8001",
      "description": "Local development"
    }
  ],
  "paths": {
    "/openapi.json": {
      "get": {
        "summary": "This OpenAPI spec.",
        "description": "Serves the spec document the agent is currently reading. Useful as a discovery anchor.",
        "responses": {
          "200": {
            "description": "OpenAPI 3.0 document",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/llms.txt": {
      "get": {
        "summary": "Agent-onboarding doc.",
        "description": "Answer.AI llms.txt convention \u2014 minimal markdown intro for AI agents.",
        "responses": {
          "200": {
            "description": "Plain-text intro",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/v1/health": {
      "get": {
        "summary": "Liveness check.",
        "description": "Returns 200 if the database is reachable; 503 otherwise.",
        "responses": {
          "200": {
            "description": "Healthy",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthResponse"
                },
                "example": {
                  "status": "ok",
                  "database": "bankproducts",
                  "plugin": "datasette-bankproducts-bpg-routes"
                }
              }
            }
          },
          "503": {
            "description": "Database unreachable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/version": {
      "get": {
        "summary": "API + DB version info.",
        "description": "Returns API + plugin versions and per-table row counts. Useful for change detection without downloading the full snapshot.",
        "responses": {
          "200": {
            "description": "Version metadata",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VersionResponse"
                },
                "example": {
                  "api_version": "1.0",
                  "plugin_version": "0.1.0",
                  "database": "bankproducts",
                  "counts": {
                    "bank": 420,
                    "product": 2790,
                    "fee": 6040,
                    "interest_rate": 1820,
                    "claim": 1240,
                    "source_document": 680
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/banks/{identifier}": {
      "get": {
        "summary": "Bank detail.",
        "description": "Returns one bank by BPG (canonical) or slug. Slug requests 301-redirect to the BPG URL.",
        "parameters": [
          {
            "name": "identifier",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "12-char BPG (e.g. BPG001AM793N) or short slug (e.g. zkb)."
          }
        ],
        "responses": {
          "200": {
            "description": "Bank found (BPG URL)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Bank"
                }
              }
            }
          },
          "301": {
            "description": "Slug \u2192 BPG canonical redirect"
          },
          "404": {
            "description": "Bank not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/banks/{identifier}/products": {
      "get": {
        "summary": "Products for a bank.",
        "description": "Returns products offered by a bank. Cursor-paginated, ordered by `id` (lexicographic).",
        "parameters": [
          {
            "name": "identifier",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/LimitQuery"
          },
          {
            "$ref": "#/components/parameters/CursorQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "Product list",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductList"
                },
                "example": {
                  "bank_id": "zkb",
                  "bank_bpg": "BPG001AM793N",
                  "count": 2,
                  "next_cursor": "eyJpZCI6InprYjpwcml2YXRrb250byJ9",
                  "products": [
                    {
                      "id": "zkb:lohnkonto",
                      "bpg": "BPG003ABC123",
                      "bank_id": "zkb",
                      "category": "current_account",
                      "name": "ZKB Lohnkonto",
                      "currency": "CHF",
                      "client_type": "private",
                      "target_group": "standard",
                      "access_tier": "free",
                      "is_tailored": 0
                    },
                    {
                      "id": "zkb:privatkonto",
                      "bpg": "BPG003KV0CVB",
                      "bank_id": "zkb",
                      "category": "current_account",
                      "name": "ZKB Privatkonto",
                      "currency": "CHF",
                      "client_type": "private",
                      "target_group": "standard",
                      "access_tier": "free",
                      "is_tailored": 0
                    }
                  ]
                }
              }
            }
          },
          "301": {
            "description": "Slug \u2192 BPG canonical redirect (query string preserved)"
          },
          "404": {
            "description": "Bank not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/products/{identifier}": {
      "get": {
        "summary": "Product detail.",
        "description": "Returns one product by BPG or slug.",
        "parameters": [
          {
            "name": "identifier",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Product found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Product"
                }
              }
            }
          },
          "301": {
            "description": "Slug \u2192 BPG canonical redirect"
          },
          "404": {
            "description": "Product not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/products/{identifier}/fees": {
      "get": {
        "summary": "Fees for a product.",
        "description": "Cursor-paginated, ordered by `id` (lexicographic). Every row carries a `source_page` reference to the original bank PDF. NULL `amount` means 'on request' (tailored) or 'not published in source'.",
        "parameters": [
          {
            "name": "identifier",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/LimitQuery"
          },
          {
            "$ref": "#/components/parameters/CursorQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "Fee list",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FeeList"
                },
                "example": {
                  "product_id": "zkb:privatkonto",
                  "product_bpg": "BPG003KV0CVB",
                  "count": 1,
                  "next_cursor": "eyJpZCI6InprYjpwcml2YXRrb250bzphY2NvdW50X21nbXQifQ",
                  "fees": [
                    {
                      "id": "zkb:privatkonto:account_mgmt",
                      "product_id": "zkb:privatkonto",
                      "bank_id": "zkb",
                      "fee_type": "account_mgmt",
                      "label": "Kontofuehrung",
                      "amount": 0.0,
                      "currency": "CHF",
                      "frequency": "monthly",
                      "tier": null,
                      "channel": null,
                      "note": "Bis 3 Konten kostenlos; 4. und weitere CHF 1/Monat",
                      "source_page": 2,
                      "valid_from": "2026-04-01",
                      "access_tier": "free"
                    }
                  ]
                }
              }
            }
          },
          "301": {
            "description": "Slug \u2192 BPG canonical redirect (query string preserved)"
          },
          "404": {
            "description": "Product not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/products/{identifier}/rates": {
      "get": {
        "summary": "Interest rates for a product.",
        "description": "Cursor-paginated, ordered by `id` (lexicographic). `rate_type` matches category per journal atom #316: mortgage_rate, consumer_credit, overdraft, credit, savings_rate, leasing_rate, penalty. `snapshot_date` is when we observed it; `valid_from` is when the bank declared it effective.",
        "parameters": [
          {
            "name": "identifier",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/LimitQuery"
          },
          {
            "$ref": "#/components/parameters/CursorQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "Rate list",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateList"
                },
                "example": {
                  "product_id": "zkb:sparkonto",
                  "product_bpg": "BPG003N9TKMP",
                  "count": 1,
                  "next_cursor": null,
                  "rates": [
                    {
                      "id": "zkb:sparkonto:credit",
                      "product_id": "zkb:sparkonto",
                      "bank_id": "zkb",
                      "rate_type": "credit",
                      "rate": null,
                      "tier_floor": null,
                      "tier_ceiling": null,
                      "tenor_months": null,
                      "currency": "CHF",
                      "snapshot_date": "2026-05-24",
                      "valid_from": "2026-04-01",
                      "note": "Auf Anfrage; ZKB-Preisverzeichnis publiziert keine Sparzinsen",
                      "source_document_id": null
                    }
                  ]
                }
              }
            }
          },
          "301": {
            "description": "Slug \u2192 BPG canonical redirect (query string preserved)"
          },
          "404": {
            "description": "Product not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/source_documents/{identifier}": {
      "get": {
        "summary": "Source document detail.",
        "description": "Returns provenance for one PDF or HTML source. BPG or integer id.",
        "parameters": [
          {
            "name": "identifier",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Source document",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SourceDocument"
                }
              }
            }
          },
          "301": {
            "description": "Integer id \u2192 BPG canonical redirect"
          },
          "404": {
            "description": "Source document not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "LimitQuery": {
        "name": "limit",
        "in": "query",
        "required": false,
        "schema": {
          "type": "integer",
          "default": 100,
          "minimum": 1,
          "maximum": 500
        },
        "description": "Page size. Default 100, max 500. Values above 500 are silently clamped."
      },
      "CursorQuery": {
        "name": "cursor",
        "in": "query",
        "required": false,
        "schema": {
          "type": "string"
        },
        "description": "Opaque pagination cursor returned by a previous response's `next_cursor`. Treat as a black box \u2014 clients should never decode or construct cursors themselves."
      }
    },
    "schemas": {
      "Bank": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Short slug, e.g. 'zkb'",
            "example": "zkb"
          },
          "bpg": {
            "type": "string",
            "description": "Permanent BPG identifier, matches `^BPG[0-9A-Z]{9}$`",
            "example": "BPG001AM793N"
          },
          "name": {
            "type": "string",
            "example": "Zuercher Kantonalbank"
          },
          "country": {
            "type": "string",
            "enum": [
              "CH",
              "LI"
            ],
            "example": "CH"
          },
          "bank_type": {
            "type": "string",
            "description": "cantonal_bank, universal_bank, cooperative_bank, private_bank, state_bank, neobank, ...",
            "example": "cantonal_bank"
          },
          "website": {
            "type": "string",
            "format": "uri",
            "nullable": true,
            "example": "https://www.zkb.ch"
          },
          "bic": {
            "type": "string",
            "nullable": true,
            "example": "ZHSZCHZZ80A"
          },
          "lei": {
            "type": "string",
            "nullable": true,
            "description": "Legal Entity Identifier (20 chars)",
            "example": "165GRDQ39W63PHVONY02"
          }
        },
        "required": [
          "id",
          "name",
          "country"
        ],
        "example": {
          "id": "zkb",
          "bpg": "BPG001AM793N",
          "name": "Zuercher Kantonalbank",
          "country": "CH",
          "bank_type": "cantonal_bank",
          "website": "https://www.zkb.ch",
          "bic": "ZHSZCHZZ80A",
          "lei": "165GRDQ39W63PHVONY02",
          "sic_iid": "700",
          "uid": "CHE-105.939.310"
        }
      },
      "Product": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "bank-slug:product-slug, e.g. zkb:privatkonto",
            "example": "zkb:privatkonto"
          },
          "bpg": {
            "type": "string",
            "example": "BPG003KV0CVB"
          },
          "bank_id": {
            "type": "string",
            "example": "zkb"
          },
          "category": {
            "type": "string",
            "description": "current_account, savings_account, pillar_3a, mortgage, ...",
            "example": "current_account"
          },
          "name": {
            "type": "string",
            "example": "ZKB Privatkonto"
          },
          "currency": {
            "type": "string",
            "default": "CHF",
            "example": "CHF"
          },
          "client_type": {
            "type": "string",
            "enum": [
              "private",
              "business",
              "institutional"
            ],
            "example": "private"
          },
          "target_group": {
            "type": "string",
            "nullable": true,
            "example": "standard"
          },
          "is_tailored": {
            "type": "integer",
            "enum": [
              0,
              1,
              null
            ],
            "nullable": true,
            "description": "0=published; 1=tailored per client; NULL=unclassified",
            "example": 0
          },
          "access_tier": {
            "type": "string",
            "enum": [
              "free",
              "pro",
              "business"
            ],
            "example": "free"
          },
          "iso_account_type": {
            "type": "string",
            "nullable": true,
            "description": "ISO 20022 ExternalCashAccountType1Code (CACC, SVGS, LOAN, ...)",
            "example": "CACC"
          }
        },
        "required": [
          "id",
          "bank_id",
          "category",
          "name"
        ],
        "example": {
          "id": "zkb:privatkonto",
          "bpg": "BPG003KV0CVB",
          "bank_id": "zkb",
          "category": "current_account",
          "name": "ZKB Privatkonto",
          "currency": "CHF",
          "client_type": "private",
          "target_group": "standard",
          "description": "Lohn- und Zahlungskonto",
          "valid_from": null,
          "source_document_id": 598,
          "access_tier": "free",
          "is_tailored": 0,
          "notice_period_days": null,
          "iso_account_type": null
        }
      },
      "Fee": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "zkb:privatkonto:account_mgmt"
          },
          "product_id": {
            "type": "string",
            "example": "zkb:privatkonto"
          },
          "bank_id": {
            "type": "string",
            "example": "zkb"
          },
          "fee_type": {
            "type": "string",
            "description": "account_mgmt, annual_fee, fx_markup, transfer_domestic, courtage, ...",
            "example": "account_mgmt"
          },
          "label": {
            "type": "string",
            "nullable": true,
            "description": "Human-readable label from the source document",
            "example": "Kontofuehrung"
          },
          "amount": {
            "type": "number",
            "nullable": true,
            "description": "NULL means 'on request' or 'not published'",
            "example": 0.0
          },
          "currency": {
            "type": "string",
            "example": "CHF"
          },
          "frequency": {
            "type": "string",
            "nullable": true,
            "description": "monthly, annual, per_use, per_annum_pct, ...",
            "example": "monthly"
          },
          "tier": {
            "type": "string",
            "nullable": true
          },
          "channel": {
            "type": "string",
            "nullable": true,
            "description": "ebanking, paper, counter, atm, app, all"
          },
          "note": {
            "type": "string",
            "nullable": true,
            "example": "Bis 3 Konten kostenlos; 4. und weitere CHF 1/Monat"
          },
          "source_page": {
            "type": "integer",
            "nullable": true,
            "description": "Page reference in the original PDF",
            "example": 2
          },
          "valid_from": {
            "type": "string",
            "format": "date",
            "example": "2026-04-01"
          },
          "access_tier": {
            "type": "string",
            "enum": [
              "free",
              "pro",
              "business"
            ],
            "example": "free"
          }
        },
        "required": [
          "id",
          "product_id",
          "fee_type"
        ],
        "example": {
          "id": "zkb:privatkonto:account_mgmt",
          "product_id": "zkb:privatkonto",
          "bank_id": "zkb",
          "fee_type": "account_mgmt",
          "label": "Kontofuehrung",
          "amount": 0.0,
          "currency": "CHF",
          "frequency": "monthly",
          "tier": null,
          "channel": null,
          "note": "Bis 3 Konten kostenlos; 4. und weitere CHF 1/Monat",
          "source_page": 2,
          "valid_from": "2026-04-01",
          "access_tier": "free"
        }
      },
      "InterestRate": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "zkb:sparkonto:credit"
          },
          "product_id": {
            "type": "string",
            "example": "zkb:sparkonto"
          },
          "bank_id": {
            "type": "string",
            "example": "zkb"
          },
          "rate_type": {
            "type": "string",
            "enum": [
              "credit",
              "savings_rate",
              "mortgage_rate",
              "consumer_credit",
              "leasing_rate",
              "overdraft",
              "penalty",
              "bonus",
              "savings",
              "lombard_rate",
              "overnight_financing"
            ],
            "example": "credit"
          },
          "rate": {
            "type": "number",
            "nullable": true,
            "description": "Percentage value (0.050 = 0.050%). NULL means 'on request' / not published.",
            "example": null
          },
          "tier_floor": {
            "type": "number",
            "nullable": true,
            "description": "Balance threshold lower bound; NULL = from zero"
          },
          "tier_ceiling": {
            "type": "number",
            "nullable": true,
            "description": "Balance threshold upper bound; NULL = unlimited"
          },
          "tenor_months": {
            "type": "integer",
            "nullable": true,
            "description": "Term length in months; NULL = no fixed term"
          },
          "currency": {
            "type": "string",
            "default": "CHF",
            "example": "CHF"
          },
          "snapshot_date": {
            "type": "string",
            "format": "date",
            "description": "Date we observed the rate",
            "example": "2026-05-24"
          },
          "valid_from": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Date the bank declared it effective",
            "example": "2026-04-01"
          },
          "note": {
            "type": "string",
            "nullable": true,
            "example": "Auf Anfrage; ZKB-Preisverzeichnis publiziert keine Sparzinsen"
          }
        },
        "required": [
          "id",
          "product_id",
          "rate_type",
          "snapshot_date"
        ],
        "example": {
          "id": "zkb:sparkonto:credit",
          "product_id": "zkb:sparkonto",
          "bank_id": "zkb",
          "rate_type": "credit",
          "rate": null,
          "tier_floor": null,
          "tier_ceiling": null,
          "tenor_months": null,
          "currency": "CHF",
          "snapshot_date": "2026-05-24",
          "valid_from": "2026-04-01",
          "note": "Auf Anfrage; ZKB-Preisverzeichnis publiziert keine Sparzinsen",
          "source_document_id": null
        }
      },
      "SourceDocument": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "example": 598
          },
          "bpg": {
            "type": "string",
            "example": "BPG00296FH17"
          },
          "bank_id": {
            "type": "string",
            "example": "zkb"
          },
          "title": {
            "type": "string",
            "example": "Preisuebersicht und Konditionen"
          },
          "filename": {
            "type": "string",
            "nullable": true,
            "example": "preisverzeichnis.pdf"
          },
          "valid_from": {
            "type": "string",
            "format": "date",
            "example": "2026-04-01"
          },
          "parsed_at": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "2026-03-31"
          },
          "language": {
            "type": "string",
            "enum": [
              "de",
              "fr",
              "it",
              "en"
            ],
            "example": "de"
          }
        },
        "example": {
          "id": 598,
          "bpg": "BPG00296FH17",
          "bank_id": "zkb",
          "title": "Preisuebersicht und Konditionen",
          "valid_from": "2026-04-01",
          "language": "de",
          "filename": "preisverzeichnis.pdf",
          "parsed_at": "2026-03-31"
        }
      },
      "ProductList": {
        "type": "object",
        "description": "Paginated envelope. `next_cursor` is null on the last page; otherwise pass it as `?cursor=...` to fetch the next.",
        "properties": {
          "bank_id": {
            "type": "string",
            "example": "zkb"
          },
          "bank_bpg": {
            "type": "string",
            "example": "BPG001AM793N"
          },
          "count": {
            "type": "integer",
            "description": "Number of items in this page (\u2264 limit)",
            "example": 18
          },
          "next_cursor": {
            "type": "string",
            "nullable": true,
            "description": "Opaque cursor for the next page; null if this was the last page",
            "example": null
          },
          "products": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Product"
            }
          }
        },
        "required": [
          "bank_id",
          "count",
          "next_cursor",
          "products"
        ]
      },
      "FeeList": {
        "type": "object",
        "description": "Paginated envelope. `next_cursor` is null on the last page.",
        "properties": {
          "product_id": {
            "type": "string",
            "example": "zkb:privatkonto"
          },
          "product_bpg": {
            "type": "string",
            "example": "BPG003KV0CVB"
          },
          "count": {
            "type": "integer",
            "example": 28
          },
          "next_cursor": {
            "type": "string",
            "nullable": true,
            "example": null
          },
          "fees": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Fee"
            }
          }
        },
        "required": [
          "product_id",
          "count",
          "next_cursor",
          "fees"
        ]
      },
      "RateList": {
        "type": "object",
        "description": "Paginated envelope. `next_cursor` is null on the last page.",
        "properties": {
          "product_id": {
            "type": "string",
            "example": "zkb:sparkonto"
          },
          "product_bpg": {
            "type": "string",
            "example": "BPG003N9TKMP"
          },
          "count": {
            "type": "integer",
            "example": 1
          },
          "next_cursor": {
            "type": "string",
            "nullable": true,
            "example": null
          },
          "rates": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/InterestRate"
            }
          }
        },
        "required": [
          "product_id",
          "count",
          "next_cursor",
          "rates"
        ]
      },
      "HealthResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok"
            ]
          },
          "database": {
            "type": "string"
          },
          "plugin": {
            "type": "string"
          }
        }
      },
      "VersionResponse": {
        "type": "object",
        "properties": {
          "api_version": {
            "type": "string",
            "example": "1.0"
          },
          "plugin_version": {
            "type": "string",
            "example": "0.1.0"
          },
          "database": {
            "type": "string"
          },
          "counts": {
            "type": "object",
            "properties": {
              "bank": {
                "type": "integer"
              },
              "product": {
                "type": "integer"
              },
              "fee": {
                "type": "integer"
              },
              "interest_rate": {
                "type": "integer"
              },
              "claim": {
                "type": "integer"
              },
              "source_document": {
                "type": "integer"
              }
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "description": "Datasette-native error envelope (ADR-001 D6 v1)",
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              false
            ]
          },
          "status": {
            "type": "integer"
          },
          "error": {
            "type": "string",
            "description": "Machine-readable error class"
          },
          "detail": {
            "type": "string",
            "description": "Human-readable explanation"
          },
          "instance": {
            "type": "string",
            "description": "URL of the failing request"
          }
        },
        "required": [
          "ok",
          "status",
          "error"
        ]
      }
    }
  }
}