{
  "openapi": "3.1.0",
  "info": {
    "title": "Unplain API",
    "version": "1.0.0",
    "summary": "Create publish-ready PDFs from documents and AI drafts.",
    "description": "Unplain turns documents and AI drafts (text, Markdown, or Word) into\npublish-ready PDFs with a cover, table of contents, pagination,\nbranding and a closing call-to-action page.\n\nThere are two public surfaces, and they authenticate differently.\n\n**The MCP server** at `/api/mcp` is how an AI client drives Unplain.\nIt speaks the Model Context Protocol over Streamable HTTP and exposes\ntwelve tools: `get_account`, `get_design_options`, `list_brand_themes`,\n`create_brand_theme`, `update_brand_theme`, `list_ebooks`,\n`create_ebook`, `get_ebook`, `update_ebook`, `generate_cover_image`,\n`export_ebook`, `get_export_status`. Authenticate with an API key\ncreated under Account settings, API keys, sent as\n`Authorization: Bearer unp_...`. Call `tools/list` for the current tool\nschemas: that is the authoritative surface, not this document.\n\n**The feature-request board** at `/api/features` is anonymous and needs\nno key. It backs the public leaderboard at\nhttps://unplain.io/feature-requests.\n\nSetup guides: https://unplain.io/developers",
    "termsOfService": "https://unplain.io/terms",
    "contact": {
      "name": "Unplain",
      "email": "info@unplain.io",
      "url": "https://unplain.io/developers"
    }
  },
  "servers": [
    {
      "url": "https://app.unplain.io",
      "description": "Unplain application API"
    }
  ],
  "externalDocs": {
    "description": "Unplain developer documentation",
    "url": "https://unplain.io/developers"
  },
  "tags": [
    {
      "name": "MCP",
      "description": "Model Context Protocol endpoint. One multiplexed JSON-RPC path, API key required.",
      "externalDocs": {
        "description": "Setup guide",
        "url": "https://unplain.io/mcp"
      }
    },
    {
      "name": "Feature requests",
      "description": "The public, anonymous feature-request leaderboard.",
      "externalDocs": {
        "description": "The board",
        "url": "https://unplain.io/feature-requests"
      }
    },
    {
      "name": "Discovery",
      "description": "Machine-readable descriptions of this API."
    }
  ],
  "paths": {
    "/api/mcp": {
      "summary": "Model Context Protocol endpoint (Streamable HTTP)",
      "description": "The single MCP endpoint. It is stateless: no `Mcp-Session-Id` is issued, so every request stands alone and no session has to be kept consistent across replicas.",
      "post": {
        "tags": [
          "MCP"
        ],
        "security": [
          {
            "ApiKey": []
          }
        ],
        "operationId": "mcpRpc",
        "summary": "Send a JSON-RPC message",
        "description": "Send one JSON-RPC request, notification or response. Per the Streamable HTTP transport the client must send `Accept: application/json, text/event-stream`; the reply is either a single JSON object or an SSE stream.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JsonRpcRequest"
              },
              "examples": {
                "toolsList": {
                  "summary": "List the available tools",
                  "value": {
                    "jsonrpc": "2.0",
                    "id": 1,
                    "method": "tools/list"
                  }
                },
                "getAccount": {
                  "summary": "Read the account's plan and limits",
                  "value": {
                    "jsonrpc": "2.0",
                    "id": 2,
                    "method": "tools/call",
                    "params": {
                      "name": "get_account",
                      "arguments": {}
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A JSON-RPC response, or an SSE stream carrying one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcResponse"
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string",
                  "description": "Server-Sent Events framing."
                }
              }
            }
          },
          "202": {
            "description": "A notification or response was accepted. No body."
          },
          "401": {
            "description": "Authentication failed. The body is a JSON-RPC error object whose `message` names both the problem and the fix, and `WWW-Authenticate` carries an RFC 6750 bearer challenge.",
            "headers": {
              "WWW-Authenticate": {
                "description": "RFC 6750 challenge, for example `Bearer realm=\"Unplain MCP\"`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcError"
                },
                "example": {
                  "jsonrpc": "2.0",
                  "error": {
                    "code": -32001,
                    "message": "No Unplain API key was sent. Add an 'Authorization: Bearer <your key>' header. Keys are created in Unplain under Account settings, API keys."
                  },
                  "id": null
                }
              }
            }
          },
          "413": {
            "description": "Request body larger than 4 MiB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcError"
                }
              }
            }
          },
          "429": {
            "description": "Too many failed authentication attempts from this address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcError"
                }
              }
            }
          },
          "503": {
            "description": "Unplain cannot verify API keys right now.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcError"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "MCP"
        ],
        "security": [
          {
            "ApiKey": []
          }
        ],
        "operationId": "mcpStream",
        "summary": "Open a server-to-client SSE stream",
        "description": "Requires `Accept: text/event-stream`. Used by clients that listen for server-initiated messages.",
        "responses": {
          "200": {
            "description": "An SSE stream.",
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed. The body is a JSON-RPC error object whose `message` names both the problem and the fix, and `WWW-Authenticate` carries an RFC 6750 bearer challenge.",
            "headers": {
              "WWW-Authenticate": {
                "description": "RFC 6750 challenge, for example `Bearer realm=\"Unplain MCP\"`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcError"
                },
                "example": {
                  "jsonrpc": "2.0",
                  "error": {
                    "code": -32001,
                    "message": "No Unplain API key was sent. Add an 'Authorization: Bearer <your key>' header. Keys are created in Unplain under Account settings, API keys."
                  },
                  "id": null
                }
              }
            }
          },
          "405": {
            "description": "This endpoint offers no stream for that request."
          },
          "413": {
            "description": "Request body larger than 4 MiB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcError"
                }
              }
            }
          },
          "429": {
            "description": "Too many failed authentication attempts from this address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcError"
                }
              }
            }
          },
          "503": {
            "description": "Unplain cannot verify API keys right now.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcError"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "MCP"
        ],
        "security": [
          {
            "ApiKey": []
          }
        ],
        "operationId": "mcpEndSession",
        "summary": "Terminate a session",
        "description": "Accepted for protocol conformance. This server is stateless, so there is no session to discard.",
        "responses": {
          "200": {
            "description": "Acknowledged."
          },
          "401": {
            "description": "Authentication failed. The body is a JSON-RPC error object whose `message` names both the problem and the fix, and `WWW-Authenticate` carries an RFC 6750 bearer challenge.",
            "headers": {
              "WWW-Authenticate": {
                "description": "RFC 6750 challenge, for example `Bearer realm=\"Unplain MCP\"`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcError"
                },
                "example": {
                  "jsonrpc": "2.0",
                  "error": {
                    "code": -32001,
                    "message": "No Unplain API key was sent. Add an 'Authorization: Bearer <your key>' header. Keys are created in Unplain under Account settings, API keys."
                  },
                  "id": null
                }
              }
            }
          },
          "413": {
            "description": "Request body larger than 4 MiB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcError"
                }
              }
            }
          },
          "429": {
            "description": "Too many failed authentication attempts from this address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcError"
                }
              }
            }
          },
          "503": {
            "description": "Unplain cannot verify API keys right now.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcError"
                }
              }
            }
          }
        }
      }
    },
    "/api/features": {
      "summary": "The public feature-request leaderboard",
      "get": {
        "tags": [
          "Feature requests"
        ],
        "operationId": "listFeatureRequests",
        "summary": "List feature requests",
        "description": "One page of the board. Requests are publicly unattributed: no username, email or other submitter detail is returned, because none is stored on the public row. `voted` reflects the calling browser's own votes and is false for a caller with no voter cookie.",
        "security": [],
        "parameters": [
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "`top` ranks active requests by votes, `new` by submission time, `shipped` lists delivered ones. Anything else is treated as `top`.",
            "schema": {
              "type": "string",
              "enum": [
                "top",
                "new",
                "shipped"
              ],
              "default": "top"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "1-based page number. Clamped to 1..10000 rather than rejected.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10000,
              "default": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One page of requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FeatureListResult"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "The list could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "list_failed"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      },
      "post": {
        "tags": [
          "Feature requests"
        ],
        "operationId": "submitFeatureRequest",
        "summary": "Submit a feature request",
        "description": "Creates a request and counts the submitter as its first vote.\nSubmitting text that already exists does not create a second card: the\ncaller's vote is added to the existing one and the reply carries\n`duplicate: true` with status 200 instead of 201.\n\nBrowser callers only. The request must come from an allowlisted origin\nand carry `Content-Type: application/json`, which together make a\ncross-site form submission impossible.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "text"
                ],
                "properties": {
                  "text": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 200,
                    "description": "The request, as one line of plain text."
                  }
                }
              },
              "example": {
                "text": "Export to EPUB as well as PDF"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "An identical request already existed. The caller's vote was added to it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubmitResult"
                }
              }
            }
          },
          "201": {
            "description": "The request was created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubmitResult"
                }
              }
            }
          },
          "400": {
            "description": "The body or the text was rejected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                },
                "example": {
                  "error": "invalid_text",
                  "reason": "too_long",
                  "message": "Keep it under 200 characters.",
                  "maxChars": 200
                }
              }
            }
          },
          "403": {
            "description": "The `Origin` header is not on the allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "origin_not_allowed"
                }
              }
            }
          },
          "415": {
            "description": "`Content-Type` was not `application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "unsupported_media_type"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "The request could not be stored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "submit_failed"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/api/features/similar": {
      "get": {
        "tags": [
          "Feature requests"
        ],
        "operationId": "findSimilarFeatureRequests",
        "summary": "Find existing requests that look like the same idea",
        "description": "Advisory only, and intended to be called while someone is typing. It never blocks a submission, and it answers `{ items: [] }` rather than an error when the query is too short or the lookup fails.",
        "security": [],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Draft text. Fewer than 3 characters returns an empty list.",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Up to three candidate matches, possibly none.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "maxItems": 3,
                      "items": {
                        "$ref": "#/components/schemas/FeatureRequest"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/api/features/{id}/vote": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "The request's UUID, from `GET /api/features`.",
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "post": {
        "tags": [
          "Feature requests"
        ],
        "operationId": "castFeatureVote",
        "summary": "Add the caller's vote",
        "description": "Idempotent. Voting twice leaves exactly one vote, and the count in the reply is recomputed by the database rather than derived from anything the client sent.",
        "security": [],
        "responses": {
          "200": {
            "description": "The recomputed vote state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VoteResult"
                }
              }
            }
          },
          "400": {
            "description": "`id` is not a UUID.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "invalid_id"
                }
              }
            }
          },
          "403": {
            "description": "The `Origin` header is not on the allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "origin_not_allowed"
                }
              }
            }
          },
          "404": {
            "description": "No request with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "not_found"
                }
              }
            }
          },
          "415": {
            "description": "`Content-Type` was not `application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "unsupported_media_type"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "The vote could not be recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "vote_failed"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      },
      "delete": {
        "tags": [
          "Feature requests"
        ],
        "operationId": "removeFeatureVote",
        "summary": "Remove the caller's vote",
        "description": "Idempotent. Removing a vote that does not exist is a success.",
        "security": [],
        "responses": {
          "200": {
            "description": "The recomputed vote state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VoteResult"
                }
              }
            }
          },
          "400": {
            "description": "`id` is not a UUID.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "invalid_id"
                }
              }
            }
          },
          "403": {
            "description": "The `Origin` header is not on the allowlist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "origin_not_allowed"
                }
              }
            }
          },
          "404": {
            "description": "No request with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "not_found"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "The vote could not be recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "vote_failed"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "tags": [
          "Discovery"
        ],
        "operationId": "getOpenApiDocument",
        "summary": "This document",
        "description": "Also served at https://unplain.io/openapi.json.",
        "security": [],
        "responses": {
          "200": {
            "description": "The OpenAPI 3.1 description of this API.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/mcp.json": {
      "get": {
        "tags": [
          "Discovery"
        ],
        "operationId": "getMcpManifest",
        "summary": "MCP server manifest",
        "description": "The Model Context Protocol `server.json` manifest, describing how to reach the MCP endpoint and what header authenticates it. Also served at https://unplain.io/.well-known/mcp.json.",
        "security": [],
        "responses": {
          "200": {
            "description": "An MCP `server.json` document.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "unp_<base64url secret>",
        "description": "An Unplain API key, created under Account settings, API keys, and sent as `Authorization: Bearer unp_...`. The key carries exactly the capabilities of the account's plan: there is no separate API tier. Scopes are granted per key and enforced per MCP tool: `brand_themes:read`, `brand_themes:write`, `projects:read`, `projects:write`, `exports:write`, `ai_cover:write`."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "description": "Every non-MCP error reply has this shape. `error` is a stable machine-readable code; branch on it rather than on the HTTP status alone.",
        "properties": {
          "error": {
            "type": "string",
            "description": "Stable error code.",
            "examples": [
              "not_found",
              "invalid_id",
              "invalid_body",
              "invalid_text",
              "origin_not_allowed",
              "unsupported_media_type",
              "rate_limited",
              "features_unavailable",
              "list_failed",
              "submit_failed",
              "vote_failed"
            ]
          },
          "message": {
            "type": "string",
            "description": "Human-readable explanation, when one adds anything."
          },
          "documentation": {
            "type": "string",
            "format": "uri",
            "description": "Where to read about this error."
          }
        }
      },
      "ValidationError": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Error"
          },
          {
            "type": "object",
            "properties": {
              "reason": {
                "type": "string",
                "description": "Which rule the text broke.",
                "examples": [
                  "too_short",
                  "too_long",
                  "empty"
                ]
              },
              "maxChars": {
                "type": "integer",
                "description": "The hard character cap."
              }
            }
          }
        ]
      },
      "FeatureRequest": {
        "type": "object",
        "required": [
          "id",
          "text",
          "status",
          "voteCount",
          "createdAt",
          "voted"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "text": {
            "type": "string",
            "maxLength": 200
          },
          "status": {
            "type": "string",
            "enum": [
              "requested",
              "planned",
              "building",
              "shipped"
            ],
            "description": "`declined` requests exist but are never returned publicly, so they cannot be resubmitted as new ideas."
          },
          "voteCount": {
            "type": "integer",
            "minimum": 0
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "voted": {
            "type": "boolean",
            "description": "Whether the calling browser has voted for this request."
          }
        }
      },
      "FeatureListResult": {
        "type": "object",
        "required": [
          "items",
          "total",
          "page",
          "pageCount",
          "pageSize",
          "sort"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FeatureRequest"
            }
          },
          "total": {
            "type": "integer",
            "minimum": 0
          },
          "page": {
            "type": "integer",
            "minimum": 1
          },
          "pageCount": {
            "type": "integer",
            "minimum": 0
          },
          "pageSize": {
            "type": "integer",
            "const": 20
          },
          "sort": {
            "type": "string",
            "enum": [
              "top",
              "new",
              "shipped"
            ]
          }
        }
      },
      "SubmitResult": {
        "type": "object",
        "required": [
          "request",
          "duplicate"
        ],
        "properties": {
          "request": {
            "$ref": "#/components/schemas/FeatureRequest"
          },
          "duplicate": {
            "type": "boolean",
            "description": "True when an identical request already existed and the caller's vote was added to it instead."
          }
        }
      },
      "VoteResult": {
        "type": "object",
        "required": [
          "voteCount",
          "voted",
          "milestone"
        ],
        "properties": {
          "voteCount": {
            "type": "integer",
            "minimum": 0
          },
          "voted": {
            "type": "boolean"
          },
          "milestone": {
            "type": "boolean",
            "description": "True once the request has reached 50 votes, the point at which it moves to the top of the build list. Derived from the count on every read, never stored."
          }
        }
      },
      "JsonRpcRequest": {
        "type": "object",
        "required": [
          "jsonrpc",
          "method"
        ],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "type": [
              "string",
              "integer"
            ]
          },
          "method": {
            "type": "string",
            "examples": [
              "initialize",
              "tools/list",
              "tools/call"
            ]
          },
          "params": {
            "type": "object"
          }
        }
      },
      "JsonRpcResponse": {
        "type": "object",
        "required": [
          "jsonrpc"
        ],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "type": [
              "string",
              "integer",
              "null"
            ]
          },
          "result": {
            "type": "object"
          },
          "error": {
            "$ref": "#/components/schemas/JsonRpcErrorObject"
          }
        }
      },
      "JsonRpcError": {
        "type": "object",
        "required": [
          "jsonrpc",
          "error",
          "id"
        ],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "error": {
            "$ref": "#/components/schemas/JsonRpcErrorObject"
          },
          "id": {
            "type": [
              "string",
              "integer",
              "null"
            ]
          }
        }
      },
      "JsonRpcErrorObject": {
        "type": "object",
        "required": [
          "code",
          "message"
        ],
        "properties": {
          "code": {
            "type": "integer"
          },
          "message": {
            "type": "string",
            "description": "Written for the person whose assistant just failed: it names the problem and the fix, and clients surface it verbatim."
          }
        }
      }
    },
    "responses": {
      "RateLimited": {
        "description": "Too many requests. `Retry-After` gives the number of seconds to wait.",
        "headers": {
          "Retry-After": {
            "description": "Seconds to wait before retrying.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "rate_limited"
            }
          }
        }
      },
      "Unavailable": {
        "description": "A dependency Unplain needs is not reachable. Retry shortly.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "features_unavailable"
            }
          }
        }
      }
    }
  }
}
