{
    "openapi": "3.0.3",
    "info": {
        "title": "AI Text Summarization & Q&A \u2014 Ksty.ch",
        "version": "1.0",
        "description": "Summarise a document, or answer questions grounded strictly in its text \u2014 structured JSON, priced per token.\n\n# AI Text Summarization & Q&A\n\nTwo calls over a block of text: **summarise it**, or **ask it a question**. Both answer with\nstructured JSON, and both are constrained to the document you send \u2014 the model is instructed to\nwork from that text alone and to say so when it cannot.\n\n```\nPOST /api/v1/text-ai\nX-Api-Key: ksty_live_...\nContent-Type: application/json\n\n{\"text\": \"<the document>\", \"length\": \"short\"}\n```\n\n```json\n{\n  \"title\": \"Q3 revenue review\",\n  \"summary\": \"Revenue fell 12% to $4.1M, driven by churn in the SMB tier...\",\n  \"key_points\": [\n    \"Revenue $4.1M, down 12% quarter on quarter\",\n    \"SMB churn rose to 4.8% monthly\",\n    \"Board approved a hiring freeze through Q1\"\n  ],\n  \"usage\": { \"input_tokens\": 1840, \"output_tokens\": 310 }\n}\n```\n\n## Grounding \u2014 what \"from the document alone\" buys you\n\nBoth endpoints run under instructions to use only the text you send: no background knowledge, no\ninference, no filling of gaps. That constraint is the point of the API. A general-purpose model\nasked \"what is this company's revenue?\" will often answer *something* \u2014 plausibly, fluently, and\nfrom the wrong source. Here, `text-ai.ask` returns `\"answered\": false` and tells you what is\nmissing instead, and every answer it does give arrives with the passages it rests on in `quotes`,\nso you can verify it without re-reading the document.\n\n> This narrows the failure mode; it does not eliminate it. Treat `quotes` as the check: an answer\n> whose quotes do not appear in your source is one to reject, and that comparison is cheap to\n> automate.\n\n## Pricing \u2014 why this API is metered per token\n\nEvery other API in this catalog charges a flat price because its work is bounded. This one calls a\npaid language model, where cost scales with how much text goes in and how much comes out, so it is\npriced the same way:\n\n| Component | Credits |\n|---|---|\n| Base fee, per call | 1 |\n| Input, per 1,000 tokens | 10 |\n| Output, per 1,000 tokens | 50 |\n\nA token is roughly four characters of English prose. The gateway **pre-charges an estimate** \u2014 your\ntext sized generously, plus the full output ceiling for the length you asked for \u2014 and the handler\nreports what the call actually used, which refunds the difference before the response returns.\n`X-Credits-Charged` is the final figure, and `usage` in the body shows the token counts behind it.\n\nBecause the estimate assumes the maximum, a long summary places a larger hold than it usually\ncosts. Expect the refund; do not budget against the hold.\n\n## Live keys only, and no per-API free allowance\n\n`text-ai` has no test sandbox and no monthly free grant. Both follow from the same fact: every call\nspends real money on a metered model upstream, so a free path to it \u2014 whether a sandbox key or a\nrefilling allowance \u2014 is not something this platform can leave open. A `ksty_test_` key gets\n`403 sandbox_disabled`.\n\nCalls draw on your normal account balance, which includes the free credits granted at signup, so\nthere is nothing to buy before your first call.\n\n## Endpoints\n\n### Summarise \u2014 `POST /api/v1/text-ai`\n\nReturns a title, a prose summary, and the specific load-bearing facts as `key_points`. `length`\ncontrols roughly how long the prose runs (`short` \u2248 60\u2013100 words, `medium` \u2248 150\u2013250, `long` \u2248\n400\u2013600); it does not change how much of the document is read. Set `language` to translate the\noutput \u2014 the document stays in its own language, the summary comes back in yours.\n\n### Ask \u2014 `POST /api/v1/text-ai.ask`\n\nAnswers `question` from the document. Read `answered` before `answer`: when it is `false`, `answer`\nexplains what the document does not cover and `quotes` is empty.\n\nRepeated questions against the same document are the pattern this endpoint is built for, and\ndocuments over about 4,000 characters are cached upstream for a few minutes so the follow-ups cost\nnoticeably less than the first. Send the same `text` byte-for-byte to benefit \u2014 any edit starts a\nnew document as far as the cache is concerned.\n\n## Limits\n\n- 200,000 characters of `text` per call, about 80 pages of prose. Split longer documents and\n  summarise the summaries; for Q&A, send only the sections that could hold the answer.\n- One language per call. A mixed-language document is summarised in whichever dominates unless you\n  set `language`.\n- No URL fetching. Pair with `html-parser` to turn a page into text first.\n\n## When to use it\n\n- Condensing support tickets, transcripts, or long email threads into something scannable.\n- Answering a fixed set of questions across a corpus of contracts or reports, with quotes to audit.\n- Screening inbound documents before a human reads them \u2014 and knowing, via `answered`, which ones\n  do not contain what you need.",
        "x-credit-cost": 25,
        "x-free-grant": {
            "type": "none",
            "amount": 0
        },
        "x-sandbox": false
    },
    "servers": [
        {
            "url": "https://ksty.ch/api/v1"
        }
    ],
    "security": [
        {
            "ApiKeyAuth": []
        }
    ],
    "paths": {
        "/text-ai": {
            "post": {
                "operationId": "text-ai_post",
                "summary": "Summarise a document into prose plus key points",
                "x-credit-cost": 25,
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "title": "Q3 revenue review",
                                    "summary": "Revenue fell 12% to $4.1M...",
                                    "key_points": [
                                        "Revenue $4.1M, down 12%",
                                        "SMB churn rose to 4.8% monthly"
                                    ],
                                    "usage": {
                                        "input_tokens": 1840,
                                        "output_tokens": 310
                                    }
                                },
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "title": {
                                            "type": "string",
                                            "description": "A short descriptive title derived from the document."
                                        },
                                        "summary": {
                                            "type": "string",
                                            "description": "The prose summary, at roughly the requested length."
                                        },
                                        "key_points": {
                                            "type": "array",
                                            "description": "The specific facts a reader must not miss, most important first. Facts rather than section headings."
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "400": {
                        "description": "invalid_request \u2014 No \"text\" was sent, it was shorter than 40 characters, or \"question\" was missing on the ask endpoint.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Missing or invalid API key",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "402": {
                        "description": "Insufficient credits \u2014 nothing is charged and the API is never reached",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "The key is not scoped to this API, or a test key was used on an API without sandbox support",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "413": {
                        "description": "text_too_large \u2014 The text exceeded 200,000 characters.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "content_refused \u2014 The model declined to process the content.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Rate limit exceeded for this key",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "502": {
                        "description": "provider_error \u2014 The model answered in a form this API could not read.\n\noutput_truncated \u2014 The answer hit its output ceiling before it was complete.\n\nThe API failed after being charged; credits are refunded automatically",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "503": {
                        "description": "provider_busy \u2014 The upstream model was rate-limited, overloaded, or did not respond in time.\n\nprovider_unavailable \u2014 This deployment has no working model credentials configured.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                },
                "description": "Reads the whole document regardless of **length** \u2014 the setting controls how long the summary runs, not how much is read, so a `short` summary of a long document costs almost as much as a `long` one on the input side and much less on the output side.",
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "text"
                                ],
                                "properties": {
                                    "text": {
                                        "type": "string",
                                        "description": "The document to summarise. 40 to 200,000 characters.",
                                        "minLength": 40,
                                        "maxLength": 200000,
                                        "example": "The board met on 14 March to review third-quarter performance..."
                                    },
                                    "length": {
                                        "type": "string",
                                        "description": "Roughly how long the prose summary should run.",
                                        "enum": [
                                            "short",
                                            "medium",
                                            "long"
                                        ],
                                        "default": "medium"
                                    },
                                    "language": {
                                        "type": "string",
                                        "description": "Language to write the summary in, e.g. \"Spanish\". Defaults to the document's own language.",
                                        "default": "auto"
                                    }
                                }
                            }
                        }
                    }
                }
            }
        },
        "/text-ai.ask": {
            "post": {
                "operationId": "text-ai_ask_post",
                "summary": "Answer a question strictly from the document",
                "x-credit-cost": 25,
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "answered": true,
                                    "answer": "Either party may terminate with 30 days written notice.",
                                    "quotes": [
                                        "Either party may terminate on 30 days written notice"
                                    ],
                                    "usage": {
                                        "input_tokens": 620,
                                        "output_tokens": 88
                                    }
                                },
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "answered": {
                                            "type": "boolean",
                                            "description": "**false** when the document does not contain the answer. Read this first."
                                        },
                                        "answer": {
                                            "type": "string",
                                            "description": "The answer, or \u2014 when answered is false \u2014 what the document does not cover."
                                        },
                                        "quotes": {
                                            "type": "array",
                                            "description": "Verbatim passages the answer rests on, for verification. Empty when answered is false."
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "400": {
                        "description": "invalid_request \u2014 No \"text\" was sent, it was shorter than 40 characters, or \"question\" was missing on the ask endpoint.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Missing or invalid API key",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "402": {
                        "description": "Insufficient credits \u2014 nothing is charged and the API is never reached",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "The key is not scoped to this API, or a test key was used on an API without sandbox support",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "413": {
                        "description": "text_too_large \u2014 The text exceeded 200,000 characters.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "content_refused \u2014 The model declined to process the content.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Rate limit exceeded for this key",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "502": {
                        "description": "provider_error \u2014 The model answered in a form this API could not read.\n\noutput_truncated \u2014 The answer hit its output ceiling before it was complete.\n\nThe API failed after being charged; credits are refunded automatically",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "503": {
                        "description": "provider_busy \u2014 The upstream model was rate-limited, overloaded, or did not respond in time.\n\nprovider_unavailable \u2014 This deployment has no working model credentials configured.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                },
                "description": "Check **answered** before reading **answer**. `false` means the document does not contain what you asked for, and `answer` says what is missing \u2014 the API is built to tell you that rather than to guess plausibly.\n\nDocuments over ~4,000 characters are cached upstream for a few minutes, so a second question against byte-identical `text` costs noticeably less than the first.",
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "text",
                                    "question"
                                ],
                                "properties": {
                                    "text": {
                                        "type": "string",
                                        "description": "The document to answer from. 40 to 200,000 characters.",
                                        "minLength": 40,
                                        "maxLength": 200000,
                                        "example": "Termination. Either party may terminate on 30 days written notice..."
                                    },
                                    "question": {
                                        "type": "string",
                                        "description": "The question to answer.",
                                        "maxLength": 1000,
                                        "example": "What is the notice period for termination?"
                                    },
                                    "language": {
                                        "type": "string",
                                        "description": "Language to answer in. Quotes stay in the document's language.",
                                        "default": "auto"
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    },
    "components": {
        "securitySchemes": {
            "ApiKeyAuth": {
                "type": "apiKey",
                "in": "header",
                "name": "X-Api-Key"
            }
        },
        "schemas": {
            "Error": {
                "type": "object",
                "properties": {
                    "error": {
                        "type": "object",
                        "properties": {
                            "code": {
                                "type": "string",
                                "description": "Stable machine-readable code \u2014 branch on this, never on the message."
                            },
                            "message": {
                                "type": "string"
                            },
                            "request_id": {
                                "type": "string"
                            }
                        }
                    }
                }
            }
        }
    }
}