Skip to content
API REFERENCE_

Every endpoint, parameter and error code.

Base URL https://api.localesense.com. Every endpoint except /v1/health needs a key. The machine-readable spec lives at /openapi.json and is generated from the same source as this page — it cannot drift.

self.sh
$ curl https://api.localesense.com/v1/self \
    -H "Authorization: Bearer ls_live_..."

{
  "geo":      { "country_code": "PK", "city": "Karachi" },
  "currency": { "code": "PKR", "decimals": 2 },
  "fx":       { "rate": "278.02", "source": "sbp" },
  "network":  { "is_vpn": false, "risk_score": 12 }
}

6

REST endpoints_

197

markets, hand-verified_

5

central-bank FX sources_

12

digit money precision_

Interactive console

Try it

Live · key-free

A real response from a throttled sandbox. Tax, commerce, network and compliance blocks are omitted here — those need a key.

Detects your own country from this request — no parameters.

Press “Send request”.

Authentication_

Authentication

Send your key as a bearer token. Keys come in two environments — ls_live_ and ls_test_ — and the plaintext is shown exactly once, when you create it. We store only its hash, so we cannot recover it for you and neither can anyone who steals our database.

authorization.txt
Authorization: Bearer ls_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

You can also restrict a key to specific IPs or domains from the dashboard — see the help guide.

Compliance & sanctions data

Informational only, not legal or compliance advice. Sanctions and risk flags are a coarse country-level overlay derived from public OFAC and FATF sources and may be out of date; verify against current OFAC guidance and screen all parties against the OFAC SDN list. You are responsible for your own compliance.

Full terms are in the Terms of Service.

Endpoints_

Endpoints

GET /v1/self Free+

The caller's own commerce profile

Detects the caller from the edge headers and returns their full commerce profile. This is the call you put in front of a checkout.

GET /v1/self
curl https://api.localesense.com/v1/self \
    -H "Authorization: Bearer ls_live_..."
GET /v1/lookup Free+

The commerce profile for any IP

Same payload as /v1/self, for an address you supply. Use it to enrich logs, orders or a CRM after the fact.

ip
Required. A public IPv4 or IPv6 address.
GET /v1/lookup
curl "https://api.localesense.com/v1/lookup?ip=203.135.62.10" \
    -H "Authorization: Bearer ls_live_..."
POST /v1/batch Pro+

Up to 100 addresses in one call

Results come back in request order. One unresolvable address does not fail the batch — it returns an error object in its own slot.

ips
Required. An array of 1-100 IP addresses.
POST /v1/batch
curl -X POST https://api.localesense.com/v1/batch \
    -H "Authorization: Bearer ls_live_..." \
    -H "Content-Type: application/json" \
    -d '{"ips": ["203.135.62.10", "8.8.8.8"]}'
POST /v1/fraud/check Business+

A card's issuing country against the IP presenting it

The signal no geo API offers: an IP in Pakistan paired with a card issued in Nigeria. Returns bin_country_match (three-valued — null when either side is genuinely unknown), the network posture, and a composite risk score. Send only the first 6-8 digits of the card; a full card number is rejected at validation.

ip
Required. The address presenting the card.
bin
Required. The first 6-8 digits of the card number. Never the full PAN.
POST /v1/fraud/check
curl -X POST https://api.localesense.com/v1/fraud/check \
    -H "Authorization: Bearer ls_live_..." \
    -H "Content-Type: application/json" \
    -d '{"ip": "203.135.62.10", "bin": "539983"}'
GET /v1/countries Free+

Every country profile

The whole moat table in one payload — not paginated, because building a country selector should not require walking pages.

GET /v1/countries
curl https://api.localesense.com/v1/countries \
    -H "Authorization: Bearer ls_live_..."
GET /v1/countries/{cc} Free+

One country profile

A single country by ISO 3166-1 alpha-2 code. No IP involved.

cc
Required. ISO 3166-1 alpha-2, e.g. PK.
GET /v1/countries/{cc}
curl https://api.localesense.com/v1/countries/PK \
    -H "Authorization: Bearer ls_live_..."
GET /v1/fx Free+

Latest central-bank rates

Every published rate against a base currency. Sourced from central banks — never a reseller.

base
Optional. ISO 4217, defaults to USD.
GET /v1/fx
curl "https://api.localesense.com/v1/fx?base=EUR" \
    -H "Authorization: Bearer ls_live_..."
GET /v1/fx/convert Free+

Convert an amount between currencies

Arbitrary-precision conversion (bcmath, scale 12). Amounts are strings, never floats — a float cannot hold money.

from
Required. ISO 4217.
to
Required. ISO 4217.
amount
Required. A decimal string.
GET /v1/fx/convert
curl "https://api.localesense.com/v1/fx/convert?from=USD&to=PKR&amount=100" \
    -H "Authorization: Bearer ls_live_..."
GET /v1/fx/historical/{date} Business+

Rates as they stood on a date

The rates a central bank published on a given day. Useful for reconciling a historical order at the rate that actually applied.

date
Required. YYYY-MM-DD.
base
Optional. ISO 4217, defaults to USD.
GET /v1/fx/historical/{date}
curl "https://api.localesense.com/v1/fx/historical/2026-01-15?base=USD" \
    -H "Authorization: Bearer ls_live_..."
GET /v1/health No key needed

FX source staleness

Which sources are healthy and which are stale. The only endpoint that needs no API key — status pages should not require credentials.

GET /v1/health
curl https://api.localesense.com/v1/health

Code samples_

Code samples

Detecting the visitor at a checkout:

PHP
checkout.php
$response = Http::withToken(config('services.localesense.key'))
    ->get('https://api.localesense.com/v1/lookup', ['ip' => $request->ip()])
    ->json();

$currency = $response['currency']['code'];      // "PKR"
$decimals = $response['currency']['decimals'];  // 2 — never assume this
JavaScript
checkout.js
const res = await fetch("https://api.localesense.com/v1/self", {
  headers: { Authorization: `Bearer ${process.env.LOCALESENSE_KEY}` },
});
const { currency, tax, commerce } = await res.json();

// Show the payment methods people there actually use:
renderCheckout(commerce.payment_methods);
Python
checkout.py
import requests

r = requests.get(
    "https://api.localesense.com/v1/lookup",
    params={"ip": request.remote_addr},
    headers={"Authorization": f"Bearer {LOCALESENSE_KEY}"},
).json()

# Money as a string: floats cannot hold currency.
rate = Decimal(r["fx"]["rate"])
Go
checkout.go
req, _ := http.NewRequest("GET", "https://api.localesense.com/v1/self", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("LOCALESENSE_KEY"))

resp, err := http.DefaultClient.Do(req)
if err != nil {
    return err // never fail the checkout on an enrichment error
}
defer resp.Body.Close()
Error codes

Error codes

Every error carries the same envelope: {"error": {"code": "...", "message": "..."}}

Code HTTP Meaning
unauthenticated 401 The key is missing, malformed, revoked or inactive.
origin_not_allowed 403 The request came from an IP or domain your key restrictions do not allow.
tier_required 403 The endpoint needs a higher tier than the key carries.
not_found 404 No profile exists for the country or date you asked for.
geo_lookup_failed 422 The address is well-formed but cannot be placed — private, reserved or unallocated.
validation_failed 422 A parameter is missing or malformed. The response names the field.
quota_exceeded 429 The key's monthly quota is spent. Resets on the first of the month.
rate_limited 429 Too many requests per minute for this tier. Retry-After says when.

Rate limits_

Rate limits and quotas

Limits are per key: 30/min on Free, 300/min on Pro, 1,000/min on Business. Monthly quotas are 10,000 / 500,000 / 5,000,000. Both return 429 — the rate limit with a Retry-After header, the quota with the date it resets. Neither silently degrades your responses, and neither bills you overage.

See pricing for the full tier table.

FAQs

API questions, answered

How do I authenticate with the LocaleSense API?

Send your key as a bearer token: Authorization: Bearer ls_live_… on every request. Keys are shown once at creation, stored only as a SHA-256 hash, and can be restricted to specific domains or IPs from the dashboard.

Can I try the API without signing up?

Yes — the try-it console on this page runs live, key-free demo requests against the real engine. When you want your own key, the free tier includes 10,000 requests a month with no card required.

What format do responses come back in?

JSON. Monetary amounts are strings (never floats), unknown values are null rather than guessed, and every FX rate carries its source and as-of date. The full machine-readable spec lives at /openapi.json.

What happens if I exceed my rate limit or quota?

You get a 429 with a Retry-After header telling you when to try again, and a structured error body naming the limit you hit. Only successful responses (status below 400) count against your monthly quota — errors are never billed.