Read on-chain shortlinks by wallet address or by unique code. No API key, no database.
| Chain | — |
| Chain ID | — |
| JSON-RPC | — |
| Explorer | — |
| Faucet | — |
| Contract | — |
| Base URL | — |
All responses are JSON with a boolean ok field. Errors return
{ "ok": false, "error": "…" } with a 4xx/5xx status. CORS is open for GET.
Returns every shortlink owned by a wallet, paginated. Reads
getLinksByOwner() and linkCountOf() straight from the contract.
| Param | Type | Required | Description |
|---|---|---|---|
| address | string | yes | EVM address, 0x + 40 hex chars |
| offset | int | no | Start index within that wallet's list. Default 0 |
| limit | int | no | Items per page, max 50. Default 20 |
Returns the full record for a single code, including owner and visit count.
Responds 404 when the code was never planted.
| Param | Type | Required | Description |
|---|---|---|---|
| code | string | yes | 3–32 chars of A-Z a-z 0-9 - _ |
Newest first, across all wallets. Same shape as the queries above with
"mode": "latest".
Lightweight lookup that returns only the destination URL. Unlike
links.php?code= it answers 200 with
"exists": false for unknown codes, which is handy for polling.
Total links, plant fee in wei, pause flag, live block number and chain ID.
The shortest form, such as https://tau.ge/hJGQLx2,
resolves on-chain and issues a 302 to the destination. It is
identical to go.php?c=hJGQLx2 and /go/hJGQLx2.
Only http and https targets are followed; anything else
is blocked. Inactive or unknown codes return 404.
| URL | Resolved by | Needs JS |
|---|---|---|
| /{code} | server (go.php) | no |
| /go/{code} | server (go.php) | no |
| go.php?c={code} | server | no |
| /r/{code} | browser (redirect.html) | yes |
The first three are byte-identical in behaviour. /r/{code} reads the
contract straight from the visitor's browser via JSON-RPC, so it works even
when PHP is unavailable; it falls back to the server if the RPC call fails.
{
"ok": true,
"mode": "address",
"address": "0xabc…",
"count": 2,
"total": 2,
"offset": 0,
"limit": 20,
"contract": "0x…",
"chainId": 677,
"items": [
{
"code": "my-link",
"shortUrl": "https://tau.ge/my-link",
"url": "https://example.com/a/very/long/path",
"owner": "0xabc…",
"createdAt": 1756500000,
"createdAtIso": "2025-08-30T12:00:00+00:00",
"clicks": 4,
"active": true
}
]
}
…
const base = '…';
// by wallet
const mine = await fetch(`${base}/api/links.php?address=${wallet}&limit=50`)
.then(r => r.json());
// by unique code
const one = await fetch(`${base}/api/resolve.php?code=my-link`)
.then(r => r.json());
console.log(one.url);
The registry is fully public, so you can skip this API and hit the RPC yourself.
import { JsonRpcProvider, Contract } from 'ethers';
const provider = new JsonRpcProvider('https://rpc.botchain.ai', 677);
const abi = [
'function getLinksByOwner(address,uint256,uint256) view returns (tuple(string code,string url,address owner,uint64 createdAt,uint64 clicks,bool active)[])',
'function getLink(string) view returns (tuple(string code,string url,address owner,uint64 createdAt,uint64 clicks,bool active))',
'function resolve(string) view returns (string)'
];
const registry = new Contract('0x…', abi, provider);
await registry.resolve('my-link');
await registry.getLinksByOwner(wallet, 0, 50);
Planting is a state change, so it happens client-side against the contract — the API is read-only by design and never holds a private key.
const registry = new Contract(address, abi, signer);
const fee = await registry.createFee();
// auto-generated 7-char code
await registry.plantAuto('https://example.com', { value: fee });
// custom code
await registry.plant('my-link', 'https://example.com', { value: fee });
limit is capped at 50 to keep eth_call payloads small.MyLink and mylink are distinct records.