{
    "openapi": "3.0.3",
    "info": {
        "title": "AI Translation \u2014 Ksty.ch",
        "version": "1.0",
        "description": "Translate text into another language \u2014 natural, context-aware output from a large language model, priced per token.\n\n# AI Translation\n\nSend text and a target language, get a translation back \u2014 structured JSON, not prose you have to\nstrip a preamble off.\n\n```\nPOST /api/v1/translate\nX-Api-Key: ksty_live_...\nContent-Type: application/json\n\n{\"text\": \"Good morning\", \"target_language\": \"Japanese\"}\n```\n\n```json\n{\n  \"translated_text\": \"\u304a\u306f\u3088\u3046\u3054\u3056\u3044\u307e\u3059\",\n  \"romanization\": \"Ohay\u014d gozaimasu\",\n  \"detected_source_language\": \"English\",\n  \"target_language\": \"Japanese\",\n  \"usage\": { \"input_tokens\": 38, \"output_tokens\": 24 }\n}\n```\n\n## Source language is optional\n\nLeave `source_language` out and the model detects it from the text. Send it when you already know\nit \u2014 a short or ambiguous phrase is easier to translate correctly with the source language pinned\ndown than guessed at. Either way, `detected_source_language` in the response tells you what the\ntext was actually treated as, so a caller who guessed wrong can see that rather than getting a\ntranslation from the wrong starting point silently.\n\n## Romanization\n\nA translation into a non-Latin script comes back with `romanization` alongside it \u2014 the\ntranslation sounded out in Latin letters, using the standard scheme for the language (Hepburn for\nJapanese, Pinyin with tone marks for Mandarin, Revised Romanization for Korean). It is the\ndifference between a string you can display and a string you can *say*.\n\n`romanization` is always present and is an empty string when there is nothing to sound out:\n\n- the translation is already in Latin script (`French`, `Spanish`, `Vietnamese`), or\n- `text` is longer than **500 characters**.\n\nThat second limit is a billing decision rather than a technical one. A romanization roughly doubles\nthe output tokens, and output is the expensive half of this API \u2014 worth it for a phrase someone is\nabout to say, wasteful on a document nobody will read aloud. Send long text in short pieces if you\ndo want it romanized throughout.\n\n## What \"faithful\" means here\n\nThe model is instructed to translate what is there \u2014 including phrasing you might not expect a\ntranslation service to touch \u2014 rather than to edit, soften, or summarise. Formatting (line breaks,\nlists, punctuation) is preserved as closely as the target language allows. This is a translator, not\na rewriter: if you want the *meaning* adapted rather than the *words* translated, that is a\ndifferent task from what this endpoint does.\n\n## Pricing \u2014 why this is metered per token\n\nLike `text-ai` and `ocr`, this calls a paid language model, so it is priced 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\nUnlike `text-ai`, where a short summary is cheap regardless of document length, translation's output\nis close to its input in size \u2014 sometimes longer, since some language pairs expand text by 15-30%,\nand roughly double on a short call that also returns a `romanization`. The gateway's pre-charge\naccounts for that with a generous per-call ceiling; `X-Credits-Charged` is what the call actually\ncost once the difference is refunded.\n\n## Live keys only, and no per-API free allowance\n\n`translate` has no test sandbox and no monthly free grant, for the same reason as `text-ai` and\n`ocr`: every call spends real money on a metered model upstream. A `ksty_test_` key gets\n`403 sandbox_disabled`. Calls draw on your normal account balance, which includes the free credits\nreleased with your first API key.\n\n## Limits\n\n- 20,000 characters of `text` per call, about 8 pages. Split longer documents and translate the\n  parts \u2014 unlike a summary, a split translation loses no accuracy from being chunked.\n- One target language per call.\n\n## When to use it\n\n- Translating support messages, product listings, or UI strings on demand rather than maintaining a\n  static translation table.\n- Localising user-generated content where a fixed phrase list will not cover what people actually\n  write.\n- Pairing with `text-ai`'s `language` parameter when you want a translated summary instead of a\n  translated original.",
        "x-credit-cost": 15,
        "x-free-grant": {
            "type": "none",
            "amount": 0
        },
        "x-sandbox": false
    },
    "servers": [
        {
            "url": "https://ksty.ch/api/v1"
        }
    ],
    "security": [
        {
            "ApiKeyAuth": []
        }
    ],
    "paths": {
        "/translate": {
            "post": {
                "operationId": "translate_post",
                "summary": "Translate text into another language",
                "x-credit-cost": 15,
                "responses": {
                    "200": {
                        "description": "Success",
                        "content": {
                            "application/json": {
                                "example": {
                                    "translated_text": "\u304a\u306f\u3088\u3046\u3054\u3056\u3044\u307e\u3059",
                                    "romanization": "Ohay\u014d gozaimasu",
                                    "detected_source_language": "English",
                                    "target_language": "Japanese",
                                    "usage": {
                                        "input_tokens": 38,
                                        "output_tokens": 24
                                    }
                                },
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "translated_text": {
                                            "type": "string",
                                            "description": "The translation."
                                        },
                                        "romanization": {
                                            "type": "string",
                                            "description": "The translation sounded out in Latin letters. Empty when the translation is already Latin script, or when **text** was longer than 500 characters."
                                        },
                                        "detected_source_language": {
                                            "type": "string",
                                            "description": "The language the model actually translated from \u2014 check this against what you expected when you set source_language."
                                        },
                                        "target_language": {
                                            "type": "string",
                                            "description": "Echoes the target_language you sent."
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "400": {
                        "description": "invalid_request \u2014 No \"text\" or \"target_language\" was sent, or \"text\" was empty.",
                        "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 20,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 translation 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": "Leave **source_language** out to have it auto-detected \u2014 `detected_source_language` in the response tells you what the model actually translated from.\n\nTranslating into a non-Latin script also returns a **romanization** of the result, for text up to 500 characters. It is an empty string otherwise.",
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "text",
                                    "target_language"
                                ],
                                "properties": {
                                    "text": {
                                        "type": "string",
                                        "description": "The text to translate. 1 to 20,000 characters.",
                                        "minLength": 1,
                                        "maxLength": 20000,
                                        "example": "Where is the nearest train station?"
                                    },
                                    "target_language": {
                                        "type": "string",
                                        "description": "Language to translate into, e.g. \"French\" or \"es\".",
                                        "maxLength": 40,
                                        "example": "French"
                                    },
                                    "source_language": {
                                        "type": "string",
                                        "description": "Language to translate from. Omit to auto-detect.",
                                        "maxLength": 40
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    },
    "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"
                            }
                        }
                    }
                }
            }
        }
    }
}