{"openapi": "3.1.0", "info": {"title": "booklint", "version": "0.2.0", "summary": "Deterministic rule checks for any agent's actions against the limits its owner wrote (trading books, spend and booking limits). A linter: no advice, no execution.", "description": "Every check response starts with verdict (FLAGGED, CLEAN, NOT_VERIFIED, REFUSED), then coverage and coverage_complete. Each rule reports passed, failed or not checked. A result with zero flags is not a pass; NOT_VERIFIED means some rules did not run."}, "servers": [{"url": "https://booklint.com"}], "paths": {"/": {"get": {"summary": "Homepage: text by default, HTML for browsers (Accept: text/html).", "operationId": "get", "responses": {"200": {"description": "OK", "content": {"text/plain": {"schema": {"type": "string"}}}}}}}, "/llms.txt": {"get": {"summary": "The agent file: a working example call first, then the notices.", "operationId": "get_llms_txt", "responses": {"200": {"description": "OK", "content": {"text/plain": {"schema": {"type": "string"}}}}}}}, "/openapi.json": {"get": {"summary": "This document.", "operationId": "get_openapi_json", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object"}}}}}}}, "/health": {"get": {"summary": "Liveness, version, outbound guard state and the plan table.", "operationId": "get_health", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object"}}}}}}}, "/v1/plans": {"get": {"summary": "The plan table: price, availability and the checks each plan runs.", "operationId": "get_v1_plans", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object"}}}}}}}, "/v1/sample": {"get": {"summary": "A public sample, ready to edit and POST to /v1/check: the spend sample (a purchasing and booking agent's actions and limits) by default or with ?name=spend, sample 1 (a trading book, stamped now) with ?name=trading, samples 3 and 4 (Fine-Print Check offers) with ?name=booking and ?name=subscription.", "operationId": "get_v1_sample", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object"}}}}}}}, "/v1/check": {"post": {"summary": "Check an agent's actions against its owner's limits: a trading book ({snapshot, rulebook, broker?, journal?}), an actions list ({actions, limits}), or one offer before booking or subscribing ({offer, limits, as_of?, page_text?}; Fine-Print Check, see OfferCheckRequest). {\"sample\": true} (sample 1) and {\"sample\": \"spend\"}, \"booking\" or \"subscription\" (samples 2 to 4) need no key and are never counted against the free caps. Free-plan results with rules not in the plan carry an additive \"upgrade\" object.", "operationId": "post_v1_check", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object"}}}}, "400": {"description": "Malformed request (REFUSED-shaped body)"}, "402": {"description": "Free lifetime cap reached"}, "413": {"description": "Body larger than 1 MB"}, "422": {"description": "Refused input (REFUSED-shaped body)"}, "429": {"description": "Free daily cap reached (Retry-After)"}}, "requestBody": {"required": true, "content": {"application/json": {"schema": {"anyOf": [{"type": "object", "description": "A trading book: {snapshot, rulebook, broker?, journal?}"}, {"type": "object", "description": "An actions list: {actions, limits}"}, {"$ref": "#/components/schemas/OfferCheckRequest"}, {"type": "object", "description": "A public sample: {\"sample\": true | \"spend\" | \"booking\" | \"subscription\"}"}]}}}}, "security": [{}, {"bearer": []}], "description": "Every check response starts with verdict (FLAGGED, CLEAN, NOT_VERIFIED, REFUSED), then coverage and coverage_complete. Each rule reports passed, failed or not checked. A result with zero flags is not a pass; NOT_VERIFIED means some rules did not run."}}, "/v1/keys": {"post": {"summary": "{\"plan\": \"free\"} returns a free key at once; {\"plan\": \"desk\"} starts the existing Desk checkout when it is available (see upgrade.available).", "operationId": "post_v1_keys", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object"}}}}, "201": {"description": "Free key issued (shown once)"}, "409": {"description": "Plan withheld"}, "503": {"description": "Checkout not configured"}}, "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object"}}}}}}, "/v1/keys/{session}": {"get": {"summary": "Once, after a completed Desk checkout: the key for that session.", "operationId": "get_v1_keys_session", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object"}}}}}, "parameters": [{"name": "session", "in": "path", "required": true, "schema": {"type": "string"}}]}}, "/mcp": {"post": {"summary": "Model Context Protocol over Streamable HTTP (JSON-RPC 2.0, stateless). Tools: get_sample (name: booking, subscription, trading or spend; returns the issues found in that sample and the next step), check_book (a book, an actions list or an offer; the same check path and caps as POST /v1/check), check_invoice (invoice / expense checker; see POST /v1/check_invoice), check_quote_math (quote math checker; see POST /v1/check_quote_math), check_budget_variance (budget-variance checker; see POST /v1/check_budget_variance), check_accounts_tieout (balance-sheet tie-out; see POST /v1/check_accounts_tieout) and get_free_key (the same as POST /v1/keys {\"plan\": \"free\"}). Every check_* tool runs its own published sample with sample=true or with no arguments, never counted against the free caps. Every refusal carries a \"next\" object.", "operationId": "post_mcp", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object"}}}}, "202": {"description": "Notification or client response accepted; no body"}, "400": {"description": "JSON-RPC parse error or invalid request"}, "413": {"description": "Body larger than 1 MB"}}, "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object"}}}}}}, "/v1/check_invoice": {"post": {"summary": "Invoice / expense checker. Send a JSON object with expected_currency (string) and lines (array of {id, vendor, date, amount, currency}). Flags duplicate IDs, duplicate vendor+amount+date entries, missing required fields and currency mismatches. Optional round_number_note (off with options.round_number_note: false). Requests with a Luhn-valid card number or valid IBAN are refused before accept (sensitive_data_refused). Free plan: counts against the shared free check cap. 1 MB limit.", "operationId": "post_v1_check_invoice", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object"}}}}}, "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object"}}}}}}, "/v1/check_quote_math": {"post": {"summary": "Quote / proposal math checker. Send a JSON object with lines (array of {id, label?, quantity, unit_price, discount_pct?, total}). Flags line-total mismatches, wrong discount bases, subtotal mismatches and grand-total mismatches. Requests with a Luhn-valid card number or valid IBAN are refused before accept (sensitive_data_refused). Free plan: counts against the shared free check cap. 1 MB limit.", "operationId": "post_v1_check_quote_math", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object"}}}}}, "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object"}}}}}}, "/v1/check_budget_variance": {"post": {"summary": "Budget-variance checker. Send a JSON object with lines (array of {line_id, budget, actual, label?}). Flags lines where the actual spend diverges from budget beyond the variance threshold. Requests with a Luhn-valid card number or valid IBAN are refused before accept (sensitive_data_refused). Free plan: counts against the shared free check cap. 1 MB limit.", "operationId": "post_v1_check_budget_variance", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object"}}}}}, "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object"}}}}}}, "/v1/check_accounts_tieout": {"post": {"summary": "Balance-sheet accounts tie-out. Send a JSON object with periods (array of {period_id, total_assets, total_liabilities, total_equity}; optional per period: start, end (ISO date), subtotals, net_income, dividends, retained_earnings_open, retained_earnings_close). Checks balance equation (assets = liabilities + equity, tolerance 0.01), subtotal sums, net income tie-out (tolerance \u00b11), and period gaps/overlaps. Requests with a Luhn-valid card number or valid IBAN are refused before accept (sensitive_data_refused). Free plan: counts against the shared free check cap. 1 MB limit.", "operationId": "post_v1_check_accounts_tieout", "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": {"type": "object"}}}}}, "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object"}}}}}}, "/terms": {"get": {"summary": "Terms of Service (plain text).", "operationId": "get_terms", "responses": {"200": {"description": "OK", "content": {"text/plain": {"schema": {"type": "string"}}}}}}}, "/privacy": {"get": {"summary": "Privacy Notice (plain text).", "operationId": "get_privacy", "responses": {"200": {"description": "OK", "content": {"text/plain": {"schema": {"type": "string"}}}}}}}}, "components": {"schemas": {"Offer": {"type": "object", "additionalProperties": false, "properties": {"merchant": {"type": "string", "description": "An identifier or domain, no spaces."}, "currency": {"type": "string", "pattern": "^[A-Z]{3}$"}, "headline_price": {"type": "number", "minimum": 0, "description": "The price shown first."}, "line_items": {"type": "array", "maxItems": 200, "items": {"type": "object", "additionalProperties": false, "required": ["amount"], "properties": {"label": {"type": "string", "maxLength": 120}, "amount": {"type": "number", "minimum": 0}, "mandatory": {"type": ["boolean", "null"]}}}}, "refund": {"type": "object", "additionalProperties": false, "properties": {"refundable": {"type": ["boolean", "null"]}, "free_cancel_until": {"type": ["string", "null"], "format": "date-time"}}}, "subscription": {"type": "object", "additionalProperties": false, "properties": {"auto_renews": {"type": ["boolean", "null"]}, "renewal_price": {"type": ["number", "null"], "minimum": 0}, "renewal_interval": {"type": ["string", "null"], "enum": ["day", "week", "month", "year", null]}, "trial_ends": {"type": ["string", "null"], "format": "date-time"}}}}}, "OfferCheckRequest": {"type": "object", "required": ["offer", "limits"], "properties": {"offer": {"$ref": "#/components/schemas/Offer"}, "limits": {"type": "object", "description": "The owner's limits: a rulebook {version, declared, rules}. Offer kinds: total_price_cap, fees_above_headline (free); refundable_required, free_cancellation_min_hours, no_auto_renew, renewal_price_cap, trial_end_min_days, page_text_consistency (Desk)."}, "as_of": {"type": "string", "format": "date-time", "description": "The current UTC time. Time rules use it; the server clock is never used."}, "page_text": {"type": "string", "maxLength": 65536, "description": "Optional plain text copied from the page; above 64 KB (UTF-8) the request is refused. booklint never fetches a URL."}}}}, "securitySchemes": {"bearer": {"type": "http", "scheme": "bearer", "description": "A key from POST /v1/keys (vdk_...)"}}}}