{
  "openapi": "3.1.0",
  "info": {
    "title": "ReadingRate Public API",
    "version": "1.0.0",
    "summary": "Book intelligence: difficulty scores, literary regard, reading time",
    "description": "Read-only access to the ReadingRate catalog: difficulty scores (0-100 composite over nine dimensions), literary regard, word counts, reading-time estimates, and metadata for thousands of literary works. These endpoints need no authentication. Personal reading data (logging sessions, saving quotes, reading analytics) is served by the ReadingRate MCP server at https://mcp.readingrate.com/mcp behind OAuth - see the oauth2 security scheme for its scopes.\n\nRate limits: approximately 120 requests per minute per IP. Responses carry RateLimit-Policy, RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset; a 429 carries Retry-After in seconds.\n\nVersioning: the version lives in the URL path (/api/v1). Within a version, changes are additive only; breaking changes ship as a new path. A retired version keeps working for at least six months after its replacement, with Deprecation and Sunset headers for the whole period. Policy: https://readingrate.com/developers",
    "contact": {
      "name": "ReadingRate",
      "url": "https://readingrate.com/contact"
    },
    "termsOfService": "https://readingrate.com/terms"
  },
  "servers": [
    {
      "url": "https://readingrate.com",
      "description": "Production"
    }
  ],
  "paths": {
    "/api/v1": {
      "get": {
        "operationId": "getApiIndex",
        "summary": "API discovery document",
        "description": "Names the available endpoints, the OpenAPI spec, the human documentation, and how authentication works. Useful as a first call for agents.",
        "responses": {
          "200": {
            "description": "Discovery document",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiIndex"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/books/{slug}": {
      "get": {
        "operationId": "getBook",
        "summary": "Get one book by slug",
        "description": "Full public intelligence for one work: difficulty score with per-dimension breakdown, literary regard, word count, reading time, authors, genres, and edition facts. Slugs are lowercase-hyphenated titles (blood-meridian, in-search-of-lost-time). Unknown slugs return a real 404 - use searchBooks to resolve a title to a slug first.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "Lowercase-hyphenated book slug, e.g. blood-meridian",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The book",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Book"
                }
              }
            }
          },
          "404": {
            "description": "No book with this slug",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Catalog lookup failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/search": {
      "get": {
        "operationId": "searchBooks",
        "summary": "Search books by title or author",
        "description": "Fuzzy search over the catalog by title and author name. Returns compact results with slugs to feed into getBook.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "Search text, e.g. a title, an author, or both",
            "schema": {
              "type": "string",
              "minLength": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum results, 1-50",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 10
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Search results",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchResponse"
                }
              }
            }
          },
          "400": {
            "description": "Missing query",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Search failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "oauth2": {
        "type": "oauth2",
        "description": "OAuth 2.0 (PKCE) for personal reading data via the ReadingRate MCP server (https://mcp.readingrate.com/mcp). The public /api/v1 endpoints in this spec do not require it. RFC 9728 protected-resource metadata: https://mcp.readingrate.com/.well-known/oauth-protected-resource",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://mcp.readingrate.com/oauth/authorize",
            "tokenUrl": "https://mcp.readingrate.com/oauth/token",
            "refreshUrl": "https://mcp.readingrate.com/oauth/token",
            "scopes": {
              "catalog:read": "Read public book intelligence (difficulty, regard, metadata, awards)",
              "user:read": "Read the signed-in reader's library, logs, quotes, and analytics",
              "user:write": "Log reading sessions, save quotes, and update the reader's library"
            }
          }
        }
      }
    },
    "schemas": {
      "ScoredValue": {
        "type": "object",
        "description": "A score on the declared scale, or null when unscored",
        "properties": {
          "score": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 100
          },
          "scale": {
            "type": "string",
            "const": "0-100"
          }
        },
        "required": [
          "score",
          "scale"
        ]
      },
      "Difficulty": {
        "type": "object",
        "description": "Composite reading difficulty with the per-dimension breakdown where scored",
        "properties": {
          "score": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 100
          },
          "scale": {
            "type": "string",
            "const": "0-100"
          },
          "dimensions": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "vocabulary": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "syntax": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "abstraction": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "information_density": {
                "type": [
                  "number",
                  "null"
                ]
              }
            }
          }
        },
        "required": [
          "score",
          "scale"
        ]
      },
      "Author": {
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "slug": {
            "type": [
              "string",
              "null"
            ],
            "description": "Author page: https://readingrate.com/authors/{slug}"
          }
        }
      },
      "Book": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "authors": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Author"
            }
          },
          "publication_year": {
            "type": [
              "integer",
              "null"
            ]
          },
          "genres": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "pages": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Page count of the primary edition"
          },
          "word_count": {
            "type": [
              "integer",
              "null"
            ]
          },
          "reading_time_minutes": {
            "type": [
              "integer",
              "null"
            ],
            "description": "At an average pace; the app personalizes this to a measured pace"
          },
          "difficulty": {
            "$ref": "#/components/schemas/Difficulty"
          },
          "literary_regard": {
            "$ref": "#/components/schemas/ScoredValue"
          },
          "cover_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Human-readable page for this book"
          }
        },
        "required": [
          "slug",
          "title",
          "authors",
          "difficulty",
          "literary_regard",
          "url"
        ]
      },
      "SearchResult": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "authors": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "publication_year": {
            "type": [
              "integer",
              "null"
            ]
          },
          "difficulty": {
            "$ref": "#/components/schemas/ScoredValue"
          },
          "genres": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "cover_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "url": {
            "type": "string",
            "format": "uri"
          }
        },
        "required": [
          "slug",
          "title",
          "url"
        ]
      },
      "SearchResponse": {
        "type": "object",
        "properties": {
          "query": {
            "type": "string"
          },
          "count": {
            "type": "integer"
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SearchResult"
            }
          }
        },
        "required": [
          "query",
          "count",
          "results"
        ]
      },
      "ApiIndex": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "openapi": {
            "type": "string",
            "format": "uri"
          },
          "documentation": {
            "type": "string",
            "format": "uri"
          },
          "endpoints": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "authentication": {
            "type": "string"
          },
          "llms_txt": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            },
            "required": [
              "code",
              "message"
            ]
          }
        },
        "required": [
          "error"
        ]
      }
    },
    "responses": {
      "RateLimited": {
        "description": "Rate limit exceeded (about 120 requests per minute per IP). Retry-After gives seconds until the window resets.",
        "headers": {
          "Retry-After": {
            "description": "Seconds until the current window resets",
            "schema": {
              "type": "integer"
            }
          },
          "RateLimit-Remaining": {
            "description": "Requests left in the current window (0 here)",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  }
}
