> For the complete documentation index, see [llms.txt](https://docs.swapkit.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.swapkit.dev/spotlights/tokenized-equities-and-geo-restrictions.md).

# Tokenized equities & geo-restrictions

SwapKit routes tokenized equities, ETFs and commodities. Their issuers restrict them in some jurisdictions, so send us your end user's IP and handle a 451.

SwapKit routes **tokenized equities, ETFs and commodities** — real-world assets, or RWAs. These are securities, and their issuers may not offer them to residents of certain jurisdictions, so they come with an enforcement layer that ordinary crypto assets don't have:

* Tokens carry a **`geoBlockRule`** on token-list responses, naming the restriction that applies.
* A quote or swap involving a restricted token from a restricted jurisdiction is rejected with **`451 Unavailable For Legal Reasons`**.
* Token discovery is **never** filtered by region — `/tokens`, `/tokens/search`, `/swapFrom` and `/swapTo` return the same list to every caller.

{% hint style="warning" %}
**There is one thing you need to do: tell us your end user's IP address.** Send it as the left-most entry of `X-Forwarded-For`. If you call our API from your own backend and don't, we can only see *your server's* location — a London user behind a Virginia datacenter is assessed as Virginia.

**A region we can't resolve is refused, not allowed**, so an absent or unusable IP now costs you RWA quotes rather than only accuracy. Nothing else is affected — an ordinary swap is never region-checked.
{% endhint %}

Everything else is automatic. If you never quote an RWA, nothing about your integration changes.

### The assets

Three issuers — Ondo, Robinhood and xStocks — across several chains and providers rather than one route, and roughly 600 contracts today: xStocks on Solana and Ethereum (`SPYx`, `NVDAx`, `GLDx`), Robinhood's equities on their own chain (`AAPL`, `AMZN`, `COIN`), Ondo's on BNB Smart Chain (`TSLAon`, `SPYon`, `GLDon`) — equities, index and bond ETFs, commodities and cash. Backed Finance's tokenized equity (`SPCXx`, on BSC) is restricted under the same rule. Identifiers follow the usual form:

```
BSC.TSLAon-0x2494b603319d4d9f9715c9f4496d9e0364b59d93
```

Fetch the live set from `/tokens` rather than hardcoding a list — it grows as issuers are added.

### Sending the end user's IP

Send the address as the **left-most** entry of `X-Forwarded-For`. Every proxy between you and us appends to the right, so the left-most entry is the one you set:

```http
POST /v3/quote HTTP/1.1
x-api-key: <your key>
content-type: application/json
x-forwarded-for: 81.2.69.160
```

{% hint style="warning" %}
**Use `X-Forwarded-For`.** `X-User-Ip` is accepted and takes precedence, but currently has no effect in production — a request carrying only that header is assessed on your server's location instead of your user's. We're investigating; until it's fixed, `X-Forwarded-For` is the only one that works end to end.
{% endhint %}

IPv4 only today. An IPv6 value is skipped rather than rejected, falling through to the next source — so an IPv6-only user with nothing else to go on ends up unassessed, and refused.

If you serve a browser client that talks to us directly, you can skip this: our edge determines the region for connections it terminates itself, and it's the last source we try.

Send it on `/v3/swap` too, not just the quote — [see below](#v3-swap-is-checked-independently).

### The `geoBlockRule` field

`GET /tokens` and `GET /tokens/search` return an optional `geoBlockRule` naming **which restriction applies**. It's present only when one does, so treat its absence as "no rule known to apply".

```json
{
  "identifier": "BSC.TSLAon-0x2494b603319d4d9f9715c9f4496d9e0364b59d93",
  "chain": "BSC",
  "ticker": "TSLAon",
  "decimals": 18,
  "geoBlockRule": "tokenized-securities"
}
```

| Value                  | Covers                                                                                                           |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `tokenized-securities` | Tokenized equities, ETFs and commodities whose issuers restrict them in sanctioned or non-serviced jurisdictions |

It's a named rule rather than a boolean so that two issuers with different published restrictions can coexist, and so you can tell *why* an asset is fenced rather than only *that* it is.

{% hint style="info" %}
**Treat the set as open.** New values appear as issuers are added. Branch on the values you know and fall back to a generic "restricted" treatment for anything unrecognised, rather than writing an exhaustive switch.
{% endhint %}

Use it to badge these assets, gate them behind a disclosure, or group them into their own category. You do **not** need it for enforcement — that happens server-side either way.

**These endpoints are never filtered by region**, so a response is safe to cache and to share across your whole user base, and a short list is never a geo-block. Enforcement happens at quote and swap time, where you get an explicit 451 rather than a silently missing asset.

If you want restricted assets out of a particular user's picker, do it client-side from `geoBlockRule` — you know your user's jurisdiction better than we do from an IP.

To pull only these assets — or only ordinary crypto — pass [`category=rwa` or `category=crypto`](/swapkit-api/tokens-list-and-search-supported-tokens.md#filtering-by-asset-category) on `/tokens` and `/tokens/search` instead of filtering a full list yourself.

### Handling a 451

A restricted asset quoted from a restricted region is rejected:

```json
{
  "message": "TSLAon is not available in your region (US)",
  "error": "geoRestrictedToken",
  "data": { "0": "TSLAon", "1": "US", "rule": "tokenized-securities" }
}
```

`data.0` is the asset's display name, `data.1` the resolved ISO 3166-1 alpha-2 country — or `unknown` when no source yielded one — and `data.rule` the rule that rejected it, the same value you saw on the token.

Build your own message from those fields rather than parsing the prose, and match on `error === "geoRestrictedToken"` rather than on the status code alone.

#### `/v3/swap` is checked independently

A `routeId` is portable, so the restriction is re-checked at execution against the assets the quote actually resolved to. **A route priced from a permitted region cannot be executed from a restricted one** — you get the same 451 at `/v3/swap`.

### How enforcement works

Worth understanding, because it explains what you will and won't observe.

**Restriction is per-token, and identity is the contract.** A token is matched on its chain plus its lowercased contract address, never on its display symbol. Renaming a ticker doesn't change whether an asset is restricted.

**Each rule owns its own jurisdiction list.** The rule on the token is the key; the countries it blocks are resolved server-side.

**An unknown region is blocked, not permitted.** If no source yields a country, the quote is refused with the same 451 and the region reported as `unknown`. Only a request already naming a restricted token can reach that check — membership is looked up before any region is resolved.

**Rejection costs nothing.** The check is an index lookup, not a lookup plus a round trip, and it runs ahead of provider calls. A 451 adds no latency and consumes no provider quota.

#### Restricted jurisdictions

For `tokenized-securities`, the policy unions an issuer's published *prohibited* list (sanctions and unregistered-securities blocks) with its *non-serviceable* list — **27 countries at the time of writing, including the United States**. It's drawn from Backed Finance's published restrictions, which Ondo's match in substance.

{% hint style="warning" %}
**This list can change without notice, and without the rule name changing.** Treat the API response as authoritative: don't replicate the list client-side, and don't assume a token permitted today stays permitted.
{% endhint %}

### What this is and isn't

This enforces a **jurisdictional restriction on specific instruments**, using the location information you provide. It is not identity verification, sanctions screening or KYC — those are separate controls, and [address screening](/swapkit-api-v2/screen-check-aml-compliance.md) continues to run as it always has.

Because the region derives from the IP you send us, **the accuracy of that IP is your responsibility**, and so is any obligation you have to establish where your users actually are. We enforce on what we are told.

### Questions

Talk to your SwapKit contact if you need a different jurisdiction policy for your user base, want RWAs suppressed from your token lists entirely, or are unsure whether your requests are carrying the end user's IP — we can confirm what we're receiving from your key.
