Nexosport API
One JSON API for sports translation. Base URL https://api.nexosport.app. Every request and response is UTF-8 JSON.
Quickstart
- Sign in with Google. You get 25,000 free credits.
- Create a key under API keys. It is shown once.
- Send your first request:
curl https://api.nexosport.app/v1/translate \
-H "Authorization: Bearer $NEXOSPORT_KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "Chelsea have kept 4 clean sheets in their last 6 games.",
"target": ["th", "vi"],
"rules": [{ "term": "clean sheet", "kind": "map", "to": { "th": "คลีนชีต", "vi": "giữ sạch lưới" } }]
}'The response:
{
"id": "req_8Kq2mZ4t1VbXw9Lc",
"source": "en",
"translations": {
"th": "เชลซีเก็บคลีนชีตได้ 4 นัดจาก 6 นัดหลังสุด",
"vi": "Chelsea đã có 4 trận giữ sạch lưới trong 6 trận gần nhất."
},
"enforced": [
{ "term": "clean sheet", "kind": "map", "source": "rule", "count": 1,
"rendered": { "th": "คลีนชีต", "vi": "giữ sạch lưới" } },
{ "term": "Chelsea", "kind": "map", "source": "termbase", "count": 1,
"rendered": { "th": "เชลซี", "vi": "Chelsea" } }
],
"warnings": [],
"failed": {},
"usage": { "characters": 55, "targets": 2, "tables": 0, "credits": 110, "refunded": 0 },
"balance": 24890,
"stats": { "segments": 1, "from_memory": 0, "model_calls": 2 }
}Authentication
Send your key in the Authorization header as Bearer tfl_live_…. Keys belong to your account and spend its credits, so call the API from your server, never from a browser or mobile app. Revoke a leaked key in the dashboard; it stops working immediately.
Translate
POST /v1/translate translates up to 30,000 characters into one or more languages in a single call.
| Field | Type | Description |
|---|---|---|
text | string | Required. Plain text or Markdown. |
target | string or string[] | Required. Up to 12 language codes, e.g. "th" or ["th","vi"]. |
source | string | Default "en". The sports termbase applies to English sources. |
format | "markdown" | "text" | Default "markdown": headings, tables, lists and links are kept byte for byte. |
rules | Rule[] | Rules for this request. See Glossary rules. |
glossary_id | string | Apply a saved glossary. Request rules win over glossary rules for the same term. |
use_termbase | boolean | Default true. Club, player, venue and competition names from the built-in sports termbase. |
tz | string | IANA time zone for dates in embedded tables. Default "UTC". |
In the response, enforced lists every locked term and how it was rendered per language, warnings lists anything worth a look (each with a target, code and severity), and failed lists languages that could not be translated. Failed languages are refunded automatically.
Numbers
Every number is replaced by a placeholder before the model runs and restored afterwards, formatted for the target locale (1.8 becomes 1,8 in Spanish). Odds and market lines keep their decimal point. A segment the model breaks is retried once, then returned in the source language with an error warning, so a wrong number never ships.
Glossary rules
"rules": [
{ "term": "UFABET Arena", "kind": "keep" },
{ "term": "clean sheet", "kind": "map", "to": { "th": "คลีนชีต", "es": "portería a cero" } },
{ "term": "kick-off", "kind": "translate", "hint": "start of the match" }
]| kind | What happens |
|---|---|
| keep | The term is locked and comes back exactly as written in the source. |
| map | The term is locked and replaced by your rendering for that language. to takes language codes; "zh" covers "zh-CN" and "*" covers every language. English plurals match too (clean sheet matches clean sheets). |
| translate | The term must not come back in the source language. If it does, we retry once and add a translate_rule_unmet warning. hint is passed to the translator. |
Matching is case-insensitive and whole-word unless you set "case_sensitive": true. Longer terms win over shorter ones.
Glossaries
GET /v1/glossaries | List glossaries with term counts. |
POST /v1/glossaries | { "name", "description"?, "terms"?: Rule[] } |
GET /v1/glossaries/:id | A glossary with every term. |
PATCH /v1/glossaries/:id | Rename or describe. |
DELETE /v1/glossaries/:id | Delete with all terms. |
POST /v1/glossaries/:id/terms | Add or update terms: JSON { "terms": Rule[] } or a CSV body with Content-Type: text/csv. |
GET /v1/glossaries/:id/terms.csv | Export as CSV. |
DELETE /v1/glossaries/:id/terms/:termId | Remove one term. |
CSV columns: term and kind are required, hint and case_sensitive are optional, and every other column is a language code holding a map rendering.
term,kind,hint,case_sensitive,th,es
clean sheet,map,,false,คลีนชีต,portería a cero
kick-off,translate,start of the match,false,,
xG,keep,,true,,Sports tables
Put a tag where a table belongs. After translation it becomes a Markdown table in the target language, with team names localised and every number straight from the feed.
## Title race
Arsenal lead by two points after four games.
{{nexo:standings league=premier-league top=6}}
## Next up
{{nexo:fixtures league=premier-league next=5}}| Tag | Options |
|---|---|
{{nexo:standings league=… top=N}} | top: rows to show (default all) |
{{nexo:fixtures league=… next=N}} | next: upcoming matches (default 5) |
{{nexo:results league=… last=N}} | last: finished matches (default 5) |
Add title=false to drop the caption. The same tables are available directly: GET /v1/sports/football/standings | fixtures | results with league, lang, tz, format=json|markdown|html and top / next / last. GET /v1/sports/football/leagues lists every supported league slug. Team names we could not localise are listed in untranslated_names (JSON) or the x-nexosport-untranslated-names header; a map rule for that name fixes it everywhere.
Long documents
For text over 30,000 characters (up to 1,000,000), queue a job with the same body as /v1/translate, plus an optional callback_url (HTTPS). We POST { "id", "status" } to it when the job finishes. Credits are charged when the job is queued; anything that fails is refunded.
POST /v1/documents → 202 { "id": "doc_…", "status": "queued" }
GET /v1/documents/:id → { "status": "queued" | "running" | "done" | "failed", "result": { …same as /v1/translate } }Errors
Errors always look like { "error": { "code", "message" } }. Every response carries an x-request-id header; quote it when you contact us.
| Status | code | Meaning |
|---|---|---|
| 400 | invalid_request | A field is missing or has the wrong shape. The message names the field. |
| 401 | unauthorized / invalid_api_key | No key, a malformed key, or a revoked key. |
| 402 | insufficient_credits | The request costs more than your balance. The body includes needed and balance. |
| 404 | glossary_not_found / unknown_league | The id or league slug does not exist on your account. |
| 413 | text_too_long | Over 30,000 characters. Send it to /v1/documents instead. |
| 415 | unsupported_media_type | The body must be JSON with Content-Type: application/json. |
| 429 | rate_limited | Too many requests for this key. Retry after a minute. |
| 502 / 503 | translation_failed / model_unavailable | Nothing could be translated. You were not charged. |
Credits and limits
- 1 credit = 1 source character translated into 1 language.
- Each rendered table costs 500 credits (per language, when embedded).
- Credits are charged before the work and refunded automatically for anything that fails.
- Credits expire 12 months after purchase; the oldest are spent first.
- Rate limits per key: 60 translate requests and 300 other requests per minute.
- Repeated sentences are served from your account's translation memory (kept 30 days after last use) and come back identical.
Languages
English into these twelve, each with the sports termbase and locale number formatting. Other codes work in beta with your rules but without the termbase.
| Code | Language | Native name |
|---|---|---|
th | Thai | ไทย |
zh-CN | Chinese (Simplified) | 简体中文 |
es | Spanish | Español |
fr | French | Français |
km | Khmer | ខ្មែរ |
lo | Lao | ລາວ |
my | Burmese | မြန်မာ |
tl | Filipino | Filipino |
pt | Portuguese | Português |
ru | Russian | Русский |
vi | Vietnamese | Tiếng Việt |
id | Indonesian | Bahasa Indonesia |