terriorGet an API key
Reference · v1

Terrior API

Rewrite any text toward how a specific region writes its language, and get told what could not safely be changed. Base URL: https://terrior.app. Requests and responses are JSON (UTF-8). Version: v1.

Getting started

Subscribe at https://terrior.app/signup. Checkout returns you to a page that shows your API key once; we store only a hash of it. Then:

curl https://terrior.app/v1/convert \
  -H 'Authorization: Bearer tk_live_…' \
  -H 'Content-Type: application/json' \
  -d '{"region":"eng-in-05-chandigarh","text":"The forthcoming update comprises fixes for everyone impacted by last week'\''s outage, and our colleagues will subsequently bolster support for anyone attempting to move their account."}'

On Starter and Growth, choose your regions first (see Choosing regions); Scale includes every region. Regions available now: cat-es-56-barcelona, cat-es-56-manresa, cat-es-56-sabadell, deu-ch-bs-basel, deu-de-04-hamburg, deu-de-05-bebra, deu-de-07-bergisch-gladbach, deu-de-07-leverkusen, deu-de-13-chemnitz, deu-de-13-dresden, eng-gb-sct, eng-in-05-chandigarh, eng-in-07-delhi, eng-in-09-ahmedabad, eng-in-13, eng-in-16-mumbai, eng-in-16-pune, eng-in-19, eng-in-19-bengaluru, eng-in-21, eng-in-25, eng-in-25-chennai, eng-in-28-kolkata, eng-in-40, eng-ke-05-nairobi, eng-ng-05-lagos, eng-us-ga-atlanta, eng-us-ks-smith-center, eng-us-mo-saint-joseph, eng-us-nc-salisbury, eng-us-pa-bloomsburg, hin-in-05-chandigarh, hin-in-07-delhi, hin-in-10, hin-in-16-mumbai, hin-in-23, hin-in-24, hin-in-24-jaipur, hin-in-34, hin-in-34-patna, hin-in-35, hin-in-35-bhopal, hin-in-36, hin-in-36-lucknow, hin-in-37-raipur, hin-in-38, hin-in-38-ranchi, hin-in-39, hin-in-39-dehradun, hun-hu-01-kiskunfelegyhaza, hun-hu-05-budapest, hun-ro-09-viscri, mar-in-16, mar-in-16-mumbai, mar-in-16-nagpur, mar-in-16-pune, pol-pl-72-wrocaw, pol-pl-77-krakow, pol-pl-78-warsaw, pol-pl-86-poznan, por-br-04, por-br-05, por-br-05-salvador, por-br-15-belo-horizonte, por-br-17, por-br-18, por-br-18-apucarana, por-br-18-arapongas, por-br-18-curitiba, por-br-21-rio-de-janeiro, por-br-22, por-br-23, por-br-26, por-br-26-lages, por-br-27-sao-paulo, por-br-29, ron-ro-03-pitesti, ron-ro-07-botosani, ron-ro-09-brasov, ron-ro-11-buzau, ron-ro-13-cluj-napoca, ron-ro-14-constanta, ron-ro-16-targoviste, ron-ro-18-galati, ron-ro-21-hunedoara, ron-ro-23-iasi, ron-ro-28-piatra-neamt, ron-ro-29-slatina, ron-ro-30-ploiesti, ron-ro-36-timisoara, ron-ro-39-ramnicu-valcea, rus-by-05-stowbtsy, rus-ru-48-moscow, rus-ru-67-saratov, spa-ar-07-buenos-aires, spa-co-34-bogota, spa-es-29-madrid, spa-es-34, spa-es-34-aviles, spa-es-34-gijon, spa-es-34-oviedo, spa-es-51-sevilla, spa-es-57, spa-es-58, spa-es-58-pontevedra, spa-es-58-vigo, spa-es-60-valencia, spa-mx-07-saltillo, spa-mx-15, spa-mx-24-los-zacatones, tam-in-25-chennai, tam-in-25-coimbatore, tam-in-25-cuddalore, tam-in-25-dharmapuri, tam-in-25-erode, tam-in-25-madurai, tam-in-25-nagapattinam, tam-in-25-namakkal, tam-in-25-thiruvarur, tam-in-25-vellore, zho-cn-04-suzhou, zho-cn-10, zho-cn-11, zho-cn-12, zho-cn-23-shanghai, zho-cn-25-weihai.

Authentication

Send your key as a bearer token on every /v1/ request: Authorization: Bearer tk_live_<id>_<secret>. Keys beginning tk_test_ belong to the staging service and are refused by the live one, and the reverse. Any problem with the key itself (missing, malformed, unknown, revoked) is the same 401, deliberately. A key that is valid on an account suspended for non-payment gets 402; a closed account, or one waiting to be enabled after signup, gets 403. Lost a key? Contact support to have it revoked and a new one issued.

Plans and limits

PlanRegionsRateConcurrent requests
Starter3 regions you choose60 requests / minute4
Growth15 regions you choose600 requests / minute16
ScaleEvery region, now and laterNo per-minute limit64

Every plan also has: conversions that are not counted or billed; at most 5,000 words per request (past that it is a batch job, and the request is refused with 403); a request body of at most 1 MiB (413). Prices are shown at checkout. Scale has no per-minute rate. On Starter and Growth the per-minute rate protects the service, not the bill, and is enforced and answers 429 with Retry-After.

Rate-limit headers

Every admitted /v1/convert and /v1/profiles response on a plan with a per-minute rate carries:

HeaderMeaning
X-RateLimit-LimitRequests allowed per minute on your plan.
X-RateLimit-RemainingRequests left in the current sliding minute.
X-RateLimit-ResetUnix time (seconds) at which the window is full again.

A 429 carries Retry-After in seconds. Back off for at least that long; the window slides, so retrying immediately is refused again.

Errors

Every error is {"error": "<a sentence saying what to do>"} with one of these statuses:

StatusWhenRetry?
400The body is not JSON, or text / region is missing or empty.No: fix the request.
401No key, or a key that is malformed, unknown or revoked.No.
402The account is suspended for non-payment. POST /v1/billing/portal still works, so you can pay.After paying.
403A region your plan does not hold (the message lists those you do); no profile for that region yet (the message lists those available); over 5,000 words in one request; or the account is closed or waiting to be enabled.No.
413Request body over 1 MiB.No.
429Over your per-minute rate, or over your plan's concurrent requests.Yes, after Retry-After.
503The service could not reach its own storage or payment provider. The detail is logged on our side, never returned.Yes, with backoff.

Choosing regions

Starter and Growth cover a number of regions that you choose. A new account holds none, and /v1/convert answers 403 for a region you have not chosen, naming the ones you hold. Choose with POST /v1/account/regions:

curl https://terrior.app/v1/account/regions \
  -H 'Authorization: Bearer tk_live_...' \
  -H 'Content-Type: application/json' \
  -d '{"add": ["cat-es-56-barcelona"]}'

A region you choose is held for 30 days before it can be released, so a swap is one call with both remove and add once the hold has passed. Moving to a smaller plan keeps the regions you have held longest. Scale includes every region, including the ones added after you subscribe, and has nothing to choose.

What is billed

Your plan's monthly fee, and nothing else. The same text for the same region always converts the same way, so convert a text once and keep the result; conversions are not counted against the fee and there is no per-call charge. Invoices, payment method and cancellation are in the billing portal (POST /v1/billing/portal).

Endpoints

POST /v1/convert

Rewrite text toward how region writes it. Only swaps that are provably safe are applied; the rest are returned as flagged for a person to decide. Syntax and structure are never changed.

Request fieldType
regionstringRequired. One of the regions in /v1/profiles.
textstringRequired, non-empty, at most 5,000 words.
{
  "applied": [
    {
      "concept": "upcoming~forthcoming",
      "offset": 4,
      "original": "forthcoming",
      "replacement": "upcoming"
    },
    {
      "concept": "includes~comprises",
      "offset": 23,
      "original": "comprises",
      "replacement": "includes"
    },
    {
      "concept": "affected~impacted",
      "offset": 52,
      "original": "impacted",
      "replacement": "affected"
    },
    {
      "concept": "later~subsequently",
      "offset": 108,
      "original": "subsequently",
      "replacement": "later"
    },
    {
      "concept": "strengthen~bolster",
      "offset": 121,
      "original": "bolster",
      "replacement": "strengthen"
    },
    {
      "concept": "trying~attempting",
      "offset": 148,
      "original": "attempting",
      "replacement": "trying"
    }
  ],
  "convert_ms": 0.0332,
  "flagged": [
    {
      "concept": "members~colleagues",
      "offset": 92,
      "original": "colleagues",
      "reason": "'members' and 'colleagues' may differ in meaning here, not only in which one the region writes; check before using; the two words are not written beside the same neighbouring words, so the swap could leave a nearby word that no longer fits; check before using",
      "replacement": "members"
    }
  ],
  "region": "eng-in-05-chandigarh",
  "text": "The upcoming update includes fixes for everyone affected by last week's outage, and our colleagues will later strengthen support for anyone trying to move their account."
}
Response field
textThe input with every applied swap made.
applied[]Swaps made. offset is a Unicode code-point offset into your input, not into the returned text.
flagged[]Swaps the profile suggests but did not make, each with a reason. Review these before publishing.
caveats[]What this region's profile does not yet know. Show them to whoever approves the copy.
convert_msServer-side conversion time in milliseconds, excluding network and queueing.

GET /v1/profiles

The regions your key can use, each with the number of rules, a content digest (changes when the profile is rebuilt), and its caveats.

{ "profiles": [ { "region": "lko", "n_rules": 83, "digest": "c8626095…", "caveats": ["…"] } ] }

GET /v1/account

Your plan, the regions you hold and when each can be released, subscription status and this billing period's usage.

{ "tenant": "t_…", "key_id": "…", "tier": "basic",
  "plan": "growth", "region_allowance": 15,
  "regions_held": [{ "region": "cat-es-56-barcelona", "since": 1791590400, "releasable_at": 1794182400 }],
  "requests_per_minute": 600, "max_concurrency": 16,
  "subscription": { "status": "active", "period_start": 1791590400, "period_end": 1794268800 },
  "usage_this_period": { "requests": 1250, "words": 52310 } }

POST /v1/account/regions

Choose or release regions on a plan that covers a number of them. Both lists are optional; removals apply first. Answers with the same body as GET /v1/account, or 400 saying what would succeed: over the plan's count, a region still inside its 30-day hold, a region with no profile, or a plan that includes every region.

{ "add": ["cat-es-56-barcelona"], "remove": [] }

POST /v1/billing/portal

Returns a one-time link to the billing portal: invoices, payment method, cancellation. Works for an account suspended for non-payment.

{ "url": "https://billing.stripe.com/p/session/…" }

GET /health no key

{ "status": "ok", "regions": ["del", "lko"] }

POST /v1/score not yet available

Scoring (which region a piece of copy reads as, and which words carry that reading) is not served by this API yet and answers 501. It is not billed.