Embed the bubble map on your website
Show the Nearbubbles bubble map of any NEAR NEP-141 token on your own site with a single <iframe>: holders, clusters and the transfers between them, always current.
For integration access contact @Hector885 on telegram.
You receive an API key tied to your website(s), with its own usage limits.
Quick start
<iframe
src="https://nearbubbles.xyz/embed/wrap.near?key=YOUR_API_KEY"
width="100%" height="600" style="border:0;border-radius:12px"
loading="lazy"
referrerpolicy="strict-origin-when-cross-origin"
title="Nearbubbles bubble map"></iframe>- Replace
wrap.nearwith the token contract you want to show. - Replace
YOUR_API_KEYwith your key. - Open the page on the website registered for your key. The map loads with Powered by Nearbubbles at the bottom left; it links to the main website.
Tokens nobody mapped before are analysed on demand: the holders appear first and the connections follow while the scan finishes, usually well under a minute. Tokens that were mapped before open immediately.
URL and parameters
https://nearbubbles.xyz/embed/{tokenContractId}?key={apiKey}[&list=0]| Part | Required | Description |
|---|---|---|
{tokenContractId} | yes | NEAR account ID of the NEP-141 token, for example wrap.near, token.sweat or usdt.tether-token.near. |
key | yes | Your API key. |
list | no | 0 hides the Address List button and panel (map only). Default: shown. |
Viewers can drag bubbles, zoom, hover for balance and share of supply, click a bubble or a line for details, open the address list and jump to the account on an explorer.
Size and layout
The map fills the iframe. Recommended minimum: 360 px high and 300 px wide. A responsive container with a fixed ratio:
<div style="position:relative;width:100%;aspect-ratio:16/10;min-height:360px">
<iframe
src="https://nearbubbles.xyz/embed/wrap.near?key=YOUR_API_KEY"
style="position:absolute;inset:0;width:100%;height:100%;border:0;border-radius:12px"
loading="lazy" referrerpolicy="strict-origin-when-cross-origin"
title="Nearbubbles bubble map"></iframe>
</div>Choosing the token from your app
Build the URL from the token contract your page already knows.
function showBubbleMap(container, tokenContractId) {
const iframe = document.createElement("iframe");
iframe.src = `https://nearbubbles.xyz/embed/${encodeURIComponent(tokenContractId)}?key=YOUR_API_KEY`;
iframe.style.cssText = "width:100%;height:600px;border:0;border-radius:12px";
iframe.referrerPolicy = "strict-origin-when-cross-origin";
iframe.title = "Nearbubbles bubble map";
container.replaceChildren(iframe);
}export function BubbleMap({ token }: { token: string }) {
return (
<iframe
key={token}
src={`https://nearbubbles.xyz/embed/${encodeURIComponent(token)}?key=${process.env.NEXT_PUBLIC_NEARBUBBLES_KEY}`}
style={{ width: "100%", height: 600, border: 0, borderRadius: 12 }}
referrerPolicy="strict-origin-when-cross-origin"
loading="lazy"
title="Nearbubbles bubble map"
/>
);
}Changing the iframe address (or the React key) loads the new token. Invalid contract IDs show an error inside the frame and cost no quota.
Your key and your website
A key only works on the websites registered with it, for example acme.com and *.acme.com. The check uses the address of the page that contains the iframe, which the browser sends automatically.
- List the exact host of every page that embeds the map.
*.acme.comcoversapp.acme.combut notacme.comitself, so list both when you need both. Ports and paths are ignored. - To test locally, ask us to add
localhostto your key, and remove it later. - Do not set
Referrer-Policy: no-referrer(orsame-origin) on the page holding the iframe: the browser would hide your address and the key is refused. The browser defaultstrict-origin-when-cross-originworks. - The key is visible in your page source because the browser has to send it. It is a publishable key, protected by the website list, rate limits and quotas. Never reuse it elsewhere. If it leaks, tell us: we rotate it and the old key stops working immediately.
If your site sends a Content-Security-Policy, allow the frame:
Content-Security-Policy: frame-src https://nearbubbles.xyz;Limits
| Limit | Meaning |
|---|---|
| Maps per minute | How many map requests the key may start per minute, across all of your visitors. |
| Maps per day | Daily quota, counted in UTC. Can be unlimited. |
| Expiry | Optional end date of the key. |
Opening a token again counts again (a cached map is served instantly, but it is still one request). When a limit is reached the embed shows a message and recovers on its own: the minute limit after a minute, the daily quota at 00:00 UTC. Need more? Contact us.
Server-to-server API
Keys with API access can fetch the map as JSON from your backend. Do not call it from a browser (the key would be exposed and cross-origin calls are blocked); use the iframe for anything a visitor sees.
curl -X POST https://nearbubbles.xyz/api/v1/maps \
-H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"contractId":"wrap.near"}'200with"status":"completed": the map is inmap.202with"status":"queued"or"processing": poll the job. The body hasprogressPercent,message,pollAfterMsand, while connections are still scanned, apreviewMapwith the verified holders.
curl https://nearbubbles.xyz/api/v1/maps/JOB_ID -H "X-API-Key: YOUR_API_KEY"{
"status": "completed",
"jobId": "…",
"contractId": "wrap.near",
"cachedAt": "2026-10-20T12:00:00.000Z",
"map": {
"token": { "name": "Wrapped NEAR", "symbol": "wNEAR", "decimals": 24, "totalSupplyDisplay": "…" },
"snapshot": { "blockHeight": 123456789, "generatedAt": "…", "candidateCount": 100, "eligibleHolderCount": 41 },
"holders": [
{ "accountId": "…", "balanceDisplay": "…", "percent": "12.345678", "clusterId": "c1", "connectionCount": 3 }
],
"edges": [
{ "id": "a.near|b.near", "source": "a.near", "target": "b.near", "transferCount": 4, "aToBCount": 3, "bToACount": 1 }
],
"clusters": [{ "clusterId": "c1", "memberCount": 5, "clusterSupplyPercent": "20.1" }]
}
}percent values are strings in percent units ("12.345678" is 12.345678 %). Amounts are decimal strings. Failed jobs return "status":"failed" with error: { code, message, retryable }.
Error codes
| HTTP | code | Meaning |
|---|---|---|
| 400 | INVALID_CONTRACT_ID | The token ID is not a valid NEAR account ID. |
| 401 | KEY_INVALID | Missing or unknown API key. |
| 403 | KEY_INVALID | Key is paused, revoked or expired, or has no API access. |
| 429 | KEY_RATE_LIMITED | Too many requests per minute. Retry after the Retry-After header. |
| 429 | KEY_QUOTA_EXCEEDED | Daily quota used up. Resets at 00:00 UTC. |
| 429 | FASTNEAR_RATE_LIMITED | The service is busy. Retry in a minute. |
| 200 | UNSUPPORTED_TOKEN | The account is not an NEP-141 token (job status failed). |
| 200 | UNSUPPORTED_ZERO_SUPPLY | The token reports a total supply of zero (job status failed). |
Troubleshooting
| Symptom | Fix |
|---|---|
| “This API key is not allowed on this website.” | The page's host is not on your key's list, or the browser hid it (see the Referrer-Policy note above). |
| “This API key is not valid.” | Typo, or the key was rotated. |
| “This API key is paused / revoked / expired.” | Contact us. |
| Empty frame, blocked by the browser | Your CSP lacks frame-src https://nearbubbles.xyz. |
| “The usage limit of this integration has been reached.” | Daily quota; it resets at 00:00 UTC. |
| The map shows no connections | The holders did not send tokens to each other: the map is clean. A notice at the bottom says so. |
Terms of use
- Keep the Powered by Nearbubbles badge visible. Do not hide it with CSS, cover it, or crop the iframe so it is cut off.
- Do not call the embed's internal endpoints directly or share your key; use the iframe, or the server API if your key has it.
- We can pause or revoke keys that are abused.
For integration access contact @Hector885 on telegram.