{
  "openapi": "3.1.0",
  "info": {
    "title": "Async Catalog API",
    "version": "1.1.0",
    "description": "Subject-independent text catalog. See api.md for session, retry and delay semantics."
  },
  "servers": [
    {
      "url": "/",
      "description": "Current server origin (domain-root deployment)"
    },
    {
      "url": "http://127.0.0.1:8080",
      "description": "Local development"
    }
  ],
  "paths": {
    "/healthz": {
      "get": {
        "summary": "Health and snapshot",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string"
                    },
                    "snapshot": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "status",
                    "snapshot"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/datasets": {
      "get": {
        "summary": "Available datasets",
        "description": "Only normal jitter up to 300 ms.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Scenario"
          },
          {
            "$ref": "#/components/parameters/Session"
          },
          {
            "$ref": "#/components/parameters/Seed"
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Manifest"
                      }
                    }
                  },
                  "required": [
                    "items"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds before retry; present for injected 429 and capacity 503."
              }
            }
          },
          "500": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds before retry; present for injected 429 and capacity 503."
              }
            }
          }
        }
      }
    },
    "/v1/datasets/{dataset}/search": {
      "get": {
        "summary": "Search documents",
        "description": "Partial scenario: first request per session may return 503, 429 or wait 5 seconds. Repeated requests do not receive injected faults. Search and links include canonical query parameters in request identity.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Scenario"
          },
          {
            "$ref": "#/components/parameters/Session"
          },
          {
            "$ref": "#/components/parameters/Seed"
          },
          {
            "$ref": "#/components/parameters/Dataset"
          },
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 512
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 10
            }
          },
          {
            "name": "tag",
            "in": "query",
            "required": false,
            "description": "",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Hit"
                      }
                    }
                  },
                  "required": [
                    "items"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds before retry; present for injected 429 and capacity 503."
              }
            }
          },
          "500": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds before retry; present for injected 429 and capacity 503."
              }
            }
          }
        }
      }
    },
    "/v1/datasets/{dataset}/documents/{id}": {
      "get": {
        "summary": "Full document",
        "description": "Partial scenario: first request per session may return 503, 429 or wait 5 seconds. Repeated requests do not receive injected faults. Search and links include canonical query parameters in request identity.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Scenario"
          },
          {
            "$ref": "#/components/parameters/Session"
          },
          {
            "$ref": "#/components/parameters/Seed"
          },
          {
            "$ref": "#/components/parameters/Dataset"
          },
          {
            "$ref": "#/components/parameters/ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Document"
                }
              }
            }
          },
          "400": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds before retry; present for injected 429 and capacity 503."
              }
            }
          },
          "500": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds before retry; present for injected 429 and capacity 503."
              }
            }
          }
        }
      }
    },
    "/v1/datasets/{dataset}/documents/{id}/links": {
      "get": {
        "summary": "Available linked IDs, deduplicated",
        "description": "Partial scenario: first request per session may return 503, 429 or wait 5 seconds. Repeated requests do not receive injected faults. Search and links include canonical query parameters in request identity.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Scenario"
          },
          {
            "$ref": "#/components/parameters/Session"
          },
          {
            "$ref": "#/components/parameters/Seed"
          },
          {
            "$ref": "#/components/parameters/Dataset"
          },
          {
            "$ref": "#/components/parameters/ID"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 20,
              "default": 5
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "id"
                        ]
                      }
                    }
                  },
                  "required": [
                    "items"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds before retry; present for injected 429 and capacity 503."
              }
            }
          },
          "500": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds before retry; present for injected 429 and capacity 503."
              }
            }
          }
        }
      }
    },
    "/v1/datasets/{dataset}/cards/{id}": {
      "get": {
        "summary": "Document card",
        "description": "Partial scenario: first request per session may return 503, 429 or wait 5 seconds. Repeated requests do not receive injected faults. Search and links include canonical query parameters in request identity.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Scenario"
          },
          {
            "$ref": "#/components/parameters/Session"
          },
          {
            "$ref": "#/components/parameters/Seed"
          },
          {
            "$ref": "#/components/parameters/Dataset"
          },
          {
            "$ref": "#/components/parameters/ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Card"
                }
              }
            }
          },
          "400": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds before retry; present for injected 429 and capacity 503."
              }
            }
          },
          "500": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds before retry; present for injected 429 and capacity 503."
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "Scenario": {
        "name": "X-Lab-Scenario",
        "in": "header",
        "required": false,
        "description": "See docs/api.md for fault algorithm.",
        "schema": {
          "type": "string",
          "enum": [
            "normal",
            "partial"
          ],
          "default": "normal"
        }
      },
      "Session": {
        "name": "X-Lab-Session",
        "in": "header",
        "required": false,
        "description": "Required for partial. New UUID per search, preserved on retries. Server tracks the first attempt per resource, including query parameters for search and links.",
        "schema": {
          "type": "string",
          "pattern": "^[a-zA-Z0-9_-]{16,128}$"
        }
      },
      "Seed": {
        "name": "X-Lab-Seed",
        "in": "header",
        "required": false,
        "description": "Optional reproducibility override. Defaults to X-Lab-Session; keep unchanged within a session.",
        "schema": {
          "type": "string",
          "maxLength": 128
        }
      },
      "Dataset": {
        "name": "dataset",
        "in": "path",
        "required": true,
        "description": "",
        "schema": {
          "type": "string"
        }
      },
      "ID": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "",
        "schema": {
          "type": "string"
        }
      }
    },
    "schemas": {
      "Credit": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "role": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          }
        },
        "required": [
          "name",
          "role"
        ]
      },
      "Source": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri"
          },
          "license": {
            "type": "string"
          },
          "license_url": {
            "type": "string",
            "format": "uri"
          },
          "credits": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Credit"
            }
          },
          "revision": {
            "type": "string"
          },
          "changes": {
            "type": "string"
          },
          "notices": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "url",
          "license",
          "license_url",
          "credits"
        ]
      },
      "Section": {
        "type": "object",
        "properties": {
          "heading": {
            "type": "string"
          },
          "text": {
            "type": "string"
          }
        },
        "required": [
          "text"
        ]
      },
      "Document": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "language": {
            "enum": [
              "en",
              "ru"
            ]
          },
          "aliases": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sections": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Section"
            }
          },
          "source": {
            "$ref": "#/components/schemas/Source"
          }
        },
        "required": [
          "id",
          "title",
          "language",
          "aliases",
          "tags",
          "sections",
          "source"
        ],
        "additionalProperties": false
      },
      "Card": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "language": {
            "type": "string"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "excerpt": {
            "type": "string"
          },
          "source": {
            "$ref": "#/components/schemas/Source"
          }
        },
        "required": [
          "id",
          "title",
          "language",
          "tags",
          "excerpt",
          "source"
        ]
      },
      "Hit": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "score": {
            "type": "number"
          }
        },
        "required": [
          "id",
          "title",
          "score"
        ]
      },
      "Manifest": {
        "type": "object",
        "properties": {
          "schema_version": {
            "type": "integer",
            "const": 1
          },
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "language": {
            "type": "string"
          },
          "license": {
            "type": "string"
          },
          "license_url": {
            "type": "string"
          },
          "source_url": {
            "type": "string"
          }
        },
        "required": [
          "schema_version",
          "id",
          "title",
          "description",
          "language",
          "license",
          "license_url",
          "source_url"
        ]
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            },
            "required": [
              "code",
              "message"
            ]
          }
        },
        "required": [
          "error"
        ]
      }
    }
  }
}
