Nexosport API

One JSON API for sports translation. Base URL https://api.nexosport.app. Every request and response is UTF-8 JSON.

Quickstart

  1. Sign in with Google. You get 25,000 free credits.
  2. Create a key under API keys. It is shown once.
  3. 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.

FieldTypeDescription
textstringRequired. Plain text or Markdown.
targetstring or string[]Required. Up to 12 language codes, e.g. "th" or ["th","vi"].
sourcestringDefault "en". The sports termbase applies to English sources.
format"markdown" | "text"Default "markdown": headings, tables, lists and links are kept byte for byte.
rulesRule[]Rules for this request. See Glossary rules.
glossary_idstringApply a saved glossary. Request rules win over glossary rules for the same term.
use_termbasebooleanDefault true. Club, player, venue and competition names from the built-in sports termbase.
tzstringIANA 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" }
]
kindWhat happens
keepThe term is locked and comes back exactly as written in the source.
mapThe 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).
translateThe 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/glossariesList glossaries with term counts.
POST /v1/glossaries{ "name", "description"?, "terms"?: Rule[] }
GET /v1/glossaries/:idA glossary with every term.
PATCH /v1/glossaries/:idRename or describe.
DELETE /v1/glossaries/:idDelete with all terms.
POST /v1/glossaries/:id/termsAdd or update terms: JSON { "terms": Rule[] } or a CSV body with Content-Type: text/csv.
GET /v1/glossaries/:id/terms.csvExport as CSV.
DELETE /v1/glossaries/:id/terms/:termIdRemove 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}}
TagOptions
{{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.

StatuscodeMeaning
400invalid_requestA field is missing or has the wrong shape. The message names the field.
401unauthorized / invalid_api_keyNo key, a malformed key, or a revoked key.
402insufficient_creditsThe request costs more than your balance. The body includes needed and balance.
404glossary_not_found / unknown_leagueThe id or league slug does not exist on your account.
413text_too_longOver 30,000 characters. Send it to /v1/documents instead.
415unsupported_media_typeThe body must be JSON with Content-Type: application/json.
429rate_limitedToo many requests for this key. Retry after a minute.
502 / 503translation_failed / model_unavailableNothing 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.

CodeLanguageNative name
thThaiไทย
zh-CNChinese (Simplified)简体中文
esSpanishEspañol
frFrenchFrançais
kmKhmerខ្មែរ
loLaoລາວ
myBurmeseမြန်မာ
tlFilipinoFilipino
ptPortuguesePortuguês
ruRussianРусский
viVietnameseTiếng Việt
idIndonesianBahasa Indonesia