Reference
Read API
The keeper (Phase 4) serves everything the site shows through a public, read-only JSON API. The site's api data adapter is built against this page, and the mock adapter returns the same shapes today. Types are defined once, in web/contract/types.ts.
Conventions
| Base URL | https://api.useanywhere.app/v1 |
| Format | JSON, UTF-8 |
| Money | US dollars as numbers with cents (2412.00). Local amounts carry a currency (ISO 4217). |
| Time | ISO 8601, UTC (2026-09-26T09:00:00Z) |
| Countries | ISO 3166-1 alpha-2, upper case (IN) |
| Handles | Lower case, without @ |
| Pages | ?offset=0&limit=50 (max 500). Responses: { rows, total, offset, limit } |
| Errors | { "code": "notFound", "message": "…" } with a 4xx/5xx status |
| Caching | Most endpoints can be cached for 10 seconds. /feed should not be cached. |
| Auth | None for reads. Writes need the X session (below). |
Reads
| Method and path | Returns | Query |
|---|---|---|
GET /stats | Stats | |
GET /stats/daily | DailyPoint[] | days (default 30) |
GET /stats/rails | RailSplit[] | |
GET /unclaimed | Page<Handle>: waiting balances, biggest first | country, min (USD), expiring=1 (within 14 days), q, offset, limit |
GET /handles?q= | Handle[]: search by handle or display name | q, limit |
GET /handles/{h} | HandleDetail: the handle, its coins, payouts, claims, expiries, notices, public log and charity | |
GET /payouts | Page<Payout>, newest first | handle, rail, offset, limit |
GET /payouts/{id} | { payout, handle, charity }: a receipt | |
GET /claims | Page<ClaimEvent>, newest first | offset, limit |
GET /expiries | Page<ExpiryEvent>, newest first | offset, limit |
GET /burns | Page<Burn>, newest first | offset, limit |
GET /posts | Page<FeedEvent>: posted claims, charity transfers and burns | offset, limit |
GET /countries | CountryStats[] | |
GET /countries/{c} | CountryDetail: stats, leaderboard, recent claims, charity | |
GET /coins | Page<Coin> | q, sort (fees, 24h, new, holders), offset, limit |
GET /coins/{mint} | CoinDetail: the coin, its recipient, daily fee claims with signatures, payouts | |
GET /feed?after= | FeedEvent[]: everything after a timestamp (ms), oldest first | after, limit (max 40) |
Example: GET /handles/lotus_loops
{
"handle": {
"handle": "lotus_loops",
"displayName": "Meera ✦ loops",
"countryCode": "IN",
"status": "waiting",
"balanceUsd": 2412,
"lifetimeUsd": 2412,
"expiresAt": "2026-11-22T06:12:00Z",
"claimPostEnabled": true,
"coins": [{ "mint": "…pump", "ticker": "LOTUS", "name": "Lotus Loops", "feesLifetimeUsd": 1632.1 }]
},
"payouts": [],
"claims": [],
"charity": { "id": "in-akshaya-patra", "name": "The Akshaya Patra Foundation", "verify": true }
}
(Shortened. The full shape is HandleDetail in web/contract/types.ts. The data above is invented.)
Writes
All writes need an X session: a cookie issued after Sign in with X (OAuth 2.0 with PKCE), whose handle must match the handle being acted on.
| Method and path | Body | Returns |
|---|---|---|
GET /auth/x/start | Redirect to X | |
GET /auth/x/callback | Sets the session, redirects to the claim flow | |
POST /claims | ClaimRequest: { handle, mode: "keep" | "give", countryCode, railId?, values?, charityId?, postEnabled } | ClaimResult: { claim, payout, pendingReason? } |
PUT /handles/{h}/rail | { railId, values } | 204. Limited to one change per 24 hours. |
POST /handles/{h}/opt-out | 204. Honored within 24 hours. |
Claim errors
code | When |
|---|---|
notSignedIn | No session, or the session is a different handle |
blocked | The chosen country is on the blocked list |
accountTooNew | The X account is under 30 days old |
nothingToClaim | The balance is zero |
invalidDetails | The payment details fail validation. details maps field to message. |
railChangeTooSoon | A second change of payment details within 24 hours |
The X scopes we ask for
| Scope | Why |
|---|---|
users.read | To read your handle, profile location and account creation date |
tweet.read | Required by X alongside users.read |
That's all, for people claiming. The @UseAnywhereApp account that posts claims uses its own app credentials (tweet.write); it never posts as you.