API reference · v1 · 10 Nov 2026
Sentriva API
The same desk a person is reading, as JSON. Tape rows, scores, verdicts, holder concentration, and the links behind each token. Poll it over HTTPS.
- Base URL
- http://localhost:8000
- Auth
- X-API-Key on account calls. Desk reads are open.
- Format
- JSON
- Desk
- Poll on refresh_sec from the tape, usually 10s.
Quickstart
- Get a key. Sign in and create one on the keys page. The secret is shown once and starts with
stv_. Keep it in an environment variable. - Read the tape. Filter by score, chain, launchpad, market cap, or pair age. One row is one token the desk is watching.
- Read the verdict.
verdictiswatch,alert,enter_candidate, orreject.final_scoreis 0–100. The full report addsreasons.
curl "http://localhost:8000/api/v1/desk?tab=all&min_score=60" \ -H "X-API-Key: $SENTRIVA_API_KEY"
Leave the key off and the same request still returns the tape. Send it when the call should be attributed to an account. GET /api/v1/keys/self is the route that requires one.
Authentication
Two credentials, for two kinds of route.
API key
Header X-API-Key. Required on GET /api/v1/keys/self. Accepted, and optional, on the public desk routes. A key looks like stv_a1b2c3d4_…. Sentriva stores a hash of the secret. The prefix, everything before the last underscore, is what the keys list shows again. A missing, revoked, or unknown key answers 401 with detail: "Invalid API key".
GET /api/v1/keys/self HTTP/1.1 Host: localhost:8000 X-API-Key: stv_a1b2c3d4_…
Session
Header Authorization: Bearer with the Privy access token. Required to create or revoke keys, to lock or unlock $SENTRIVA, and to read or update the profile. The website attaches it after you open the app. Keep session tokens out of agents. When the server has no Privy app configured, these routes answer 503.
Locks and tiers
An account starts on Explorer. Locking $SENTRIVA raises it. The tier in force is the highest lock that is still inside its window. Unlocking early steps the account back to the next active lock, or to Explorer when none remain. GET /api/v1/keys/self returns the tier and the published request ceiling for that tier.
| Tier | Lock | Window | Ceiling |
|---|---|---|---|
| Explorer (free) | — | — | 2 / s, burst 4 |
| Builder | 1,000 $SENTRIVA | 30 days | 10 / s, burst 20 |
| Pro | 10,000 $SENTRIVA | 90 days | 200 / s, burst 400 |
| Enterprise | 100,000 $SENTRIVA | 180 days | 2,000 / s, burst 4,000 |
Enterprise is the institutional tier in JSON. GET /api/v1/pricing also returns a dollar figure and a credit schedule for each pack. The live account tier moves when a lock is created or unlocked.
Errors
Failures use one JSON body. A single reason is a string on detail. A validation failure is an array of objects with loc, msg, and type. Switch on the status, then read detail.
{ "detail": "Invalid API key" }| Status | When | What to do |
|---|---|---|
| 400 | Empty resolve query, a lock below the tier minimum, or a sixth active key. | Read detail and fix the request. |
| 401 | The API key is missing, unknown, or revoked, or the session token does not verify. | Check X-API-Key, or sign in again. |
| 404 | Unknown token, unknown key id, or a lock that is not on this account. | Check the path. |
| 422 | A field failed validation. lock_days must be 7–365. amount must be above 0. tier_unlocked is builder, pro, or institutional. | Read the detail array. |
| 503 | The desk is disabled, or Privy is not configured for a session route. | Retry later. detail names which one. |
GET/api/v1/desk
The desk page: the meme tape, the latest alert per token, or the two watchlists. Public. Query parameters narrow the tape. The launchpad menu is built before those filters, so choosing a pad does not shrink the menu.
Parameters
| Name | In | Description |
|---|---|---|
| tab | query | all (default), alerts, or watchlists. Anything else is treated as all. |
| min_score | query | Number. Drop tape rows under this final_score. Default 0. |
| chain | query | solana, base, bsc, or robinhood. Omit for every chain. |
| launchpad | query | Case-insensitive exact display name, such as pump.fun, StonkFun, pons v2, or flap v2. |
| alert_only | query | true keeps tape rows the scorer has already flagged. |
| stock_pairs | query | true keeps pairs quoted in a stock asset. |
| has_links | query | true keeps rows with a website, X, Telegram, or Discord. |
| min_mcap, max_mcap | query | Market cap bounds, in USD, as numbers. |
| min_age_min, max_age_min | query | Pair age bounds, in minutes. |
Response
tab=all fills meme_rows from the live window, about the last 20 minutes. tab=alerts fills alerts with one latest row per token, newest first, up to 80. tab=watchlists fills defi_core and meme_hunt. 503 when the desk is off.
{
"tab": "all",
"meme_rows": [
{
"id": "solana:7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
"chain": "solana",
"address": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
"symbol": "EXAMPLE",
"verdict": "enter_candidate",
"final_score": 72,
"price": 0.0002653,
"mcap": 265300,
"liq": 48200,
"vol1h": 9100,
"holders": 807,
"launchpad": "pump.fun",
"quote": "SOL",
"pair_age": "9.8h",
"pair_age_min": 588,
"age_label": "12s",
"graduated": true,
"has_links": true
}
],
"alerts": [],
"launchpads": ["pump.fun", "StonkFun", "pons v2", "flap v2"],
"desk_chains": ["base", "bsc", "robinhood", "solana"],
"refresh_sec": 10,
"tick_iso": "2026-11-10T12:00:00+00:00",
"tick_age": "8s",
"agent": { "last_meme_at": "2026-11-10T12:00:00+00:00" },
"integrations": { "telegram": false, "dune": true, "nansen": false, "arkham": false, "insightx": false }
}Robinhood rows are labeled pons v2. BSC rows are labeled flap v2. A Solana mint that StonkFun launched is labeled StonkFun, including after it graduates. A pad with nothing in the current window still appears in launchpads when it is in the catalog, and filtering to it returns an empty tape.
Alert alert_type is one of MEME_CLEAN_LAUNCH, CLUSTER_BREAK, LIQ_PULL, DEFI_SM_IN, SM_EXIT. Each alert also carries token_id, score, reasons, links, symbol, chain, current_verdict, and current_score.
The tape row
One object in meme_rows. Watchlist rows are the same object plus pinned, expires_at, and expires_label.
| Field | Type | Meaning |
|---|---|---|
| id | string | chain:address. This is the token id used everywhere else. |
| symbol, chain, address | string | Display symbol, chain slug, and the mint or contract. |
| verdict | string | watch, alert, enter_candidate, or reject. |
| final_score | number | 0–100. The number the tape sorts and filters on. |
| price, mcap, liq, vol1h | number? | USD. Null when the venue did not report it. |
| holders | integer? | Holder count. Falls back to the clustered unique-holder figure. |
| launchpad | string? | Display name. Null when no origin was found. |
| dex_id, quote | string? | Current pool DEX, and the quote symbol (SOL, WETH, STONK, …). |
| pair_age, pair_age_min | string?, number? | How old the pair is. The string is 75m, 8.8h, 77d, 4.2mo, or years. |
| age_label | string? | How long since this snapshot was written, such as 12s. |
| graduated | boolean? | The launchpad curve has completed. |
| stock_pair | boolean | The quote is a stock asset. |
| cluster_pct | string? | Largest holder cluster, already formatted, such as 12%. |
| has_links | boolean | A website, X, Telegram, or Discord is on the profile. |
| dexscreener, fomo | string? | Outbound links for the pair and the token. |
GET/api/v1/desk/status
Whether the desk database is configured and the agent loop has reported state. Public. No parameters.
{
"enabled": true,
"ready": true,
"agent": { "last_meme_at": "2026-11-10T12:00:00+00:00", "last_tick_at": "2026-11-10T12:00:00+00:00" }
}ready: false with an error string means the database is configured and the status read failed.
GET/api/v1/desk/tokens/{chain}/{address}
The full report for one indexed token: market, venue, profile, scores, reasons, holders, and outbound links. Public. This read returns the snapshot stored in the desk database. The desk agent refreshes that row on its cadence, about every 10 seconds for the meme tape. The request does not call DexScreener.
Parameters
| Name | In | Description |
|---|---|---|
| chain | path | solana, base, bsc, or robinhood. Lowercase. |
| address | path | Mint or contract. Case-sensitive on Solana. |
Responses
200 with snap, links, dune_chart, dune_key, and nansen. 404 unknown token when the desk has never indexed it. 503 when the desk is off.
links carries dexscreener, dexscreener_embed, fomo, arkham, and dashboard (the site path /t/{id}). The embed URL uses a dark chart theme; swap the theme tokens when you render it on a light page.
curl "http://localhost:8000/api/v1/desk/tokens/solana/7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU"
POST/api/v1/desk/tokens/{chain}/{address}/refresh
Re-runs evaluation for a token the desk already holds, writes the new snapshot, and returns that stored report. The agent keeps every other row current between clicks. 404 when the token is unknown. No body. The desk note at GET /api/v1/desk/tokens/{chain}/{address}/brief is also stored. A background pass writes it, so the read does not wait on the model.
curl -X POST "http://localhost:8000/api/v1/desk/tokens/solana/7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU/refresh"
POST/api/v1/desk/tokens/{chain}/{address}/dune
Runs the Dune proof queries for an indexed token and stores the series on snap.dune, then returns the report. 404 when the token is unknown. The report field dune_key says whether this server has a Dune key configured.
curl -X POST "http://localhost:8000/api/v1/desk/tokens/solana/7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU/dune"
The snapshot
snap on the token report. The tape is a flat projection of this object. The report returns the stored snapshot, so venue.launchpad can still be the origin name while the tape shows the display name (pons v2 on Robinhood, flap v2 on BSC).
Identity and market
| Field | Type | Meaning |
|---|---|---|
| id, desk, chain, address, symbol | string | desk is meme or defi. |
| pairAddress | string? | The pool the market figures come from. |
| market.priceUsd, mcapUsd, liqUsd | number? | USD. |
| market.vol1h, vol24h | number? | USD volume. |
| market.buys1h, sells1h | integer? | Trade counts over the last hour. |
| market.priceChange1h, priceChange24h | number? | Already percent. −23.3 means −23.3%, not a fraction. |
| market.pairAgeMin | number? | Pair age in minutes at the last write. |
| market.pairCreatedAt | integer? | Pair creation time, unix milliseconds. |
| updatedAt | string | ISO time of this snapshot. |
Venue, profile, holders
| Field | Type | Meaning |
|---|---|---|
| venue.dexId | string? | Pool DEX, such as raydium, uniswap, or meteora. |
| venue.launchpad | string? | Stored origin. Empty when inference found nothing at ingest. |
| venue.launchpadGraduated | boolean? | The curve completed. |
| venue.holders | integer? | Holder count. |
| venue.quoteSymbol | string? | SOL, WETH, STONK, and so on. |
| venue.labels | string[] | Venue tags, such as a pool version. |
| profile.imageUrl, website, twitter, telegram, discord | string? | Socials. twitter is the X URL. |
| cluster.decentralizationScore | number? | 0–100. Higher is more spread out. |
| cluster.topClusterPct, top10Pct, snipersPct, insidersPct, devPct, bundlersPct | number? | Fractions. 0.38 is 38%. |
| cluster.topHolders[] | object[] | owner, pct (fraction), tag, insider, isContract. Up to the holders the enricher kept. |
| reasons | string[] | Why the score landed where it did. Cleared and rewritten on every rescore. |
{
"scores": { "quality": 72.8, "timing": 70, "risk": 0, "final": 72 },
"verdict": "enter_candidate",
"reasons": ["Liquidity holds a $5k clip", "Holders are spread"],
"cluster": {
"source": "rugcheck",
"decentralizationScore": 61,
"topClusterPct": 0.12,
"top10Pct": 0.28,
"topHolders": [{ "owner": "Wallet…", "pct": 0.041, "insider": false, "isContract": false }]
}
}Scores and verdicts
scores.final is the number. quality is the structure of the pool and the holders. timing is how the tape is trading now. risk rises when concentration, a liquidity pull, or a smart-money exit is in the snapshot. The verdict is the label the desk shows next to that number.
| verdict | Reading |
|---|---|
| enter_candidate | The score cleared the desk line and no kill condition fired. |
| alert | Worth a look. A condition fired, or the score is close. |
| watch | Indexed, and not yet a candidate. |
| reject | A kill condition fired, such as a holder cluster past the cut or a liquidity pull. |
Kill alerts that match those verdicts are CLUSTER_BREAK (largest cluster at 45% or more), LIQ_PULL (liquidity fell under 45% of the previous read), and SM_EXIT (smart-money netflow over 24h is negative on a DeFi row). The same token and type stays quiet for the cooldown, about 45 minutes.
GET/api/v1/resolve/{q}
Turn a paste into the chain and address the token route expects. Public. q is a path segment, so encode slashes in a URL.
| Paste | Result |
|---|---|
| https://dexscreener.com/{chain}/{address} | kind link, with that chain and address. |
| https://pump.fun/coin/{mint} | kind link, chain solana. |
| A bare base58 mint, 32 characters or longer | kind address, chain solana. |
| A bare 0x contract, 42 characters | kind address. The chain is the one with the deepest pool for that contract. |
| An address with no market, or any other paste | 400. detail explains what to paste. |
A contract address is enough. The server asks the market which chain that address trades on and returns that chain with the address. A DexScreener link still wins when the paste includes one.
{ "chain": "solana", "address": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU", "kind": "address" }GET/api/v1/pricing
Packs, the published credit schedule, and the auth header. Public. This is the payload the pricing page renders. No parameters.
{
"base_url": "http://localhost:8000",
"schema": "sentriva.pricing/1",
"auth_header": "X-API-Key",
"credit_costs": { "desk_tape": 0, "token_detail": 1, "token_refresh": 2, "watchlist_write": 1 },
"packs": [
{
"name": "builder",
"label": "Builder",
"price_usd": "25",
"sent_lock": { "amount": 1000, "days": 30 },
"rate_limit": { "per_s": 10, "burst": 20 },
"features": ["Full desk API", "Token detail + refresh", "10 req/s"]
}
],
"note": "Keys are issued from /keys. The tier follows the account's active $SENTRIVA lock."
}GET/api/v1/keys/self
The account behind this API key: tier, published ceiling, and when the account was created. Requires X-API-Key. unlimited is true on the institutional tier.
{
"tier": "builder",
"status": "active",
"email": "ada@example.com",
"wallet_address": "0xabc…",
"unlimited": false,
"rate_limit": { "per_s": 10, "burst": 20 },
"created_at": "2026-11-10T12:00:00+00:00"
}POST/api/v1/api-keys
Issue a key for the signed-in account. Requires the session bearer. At most five keys can be active. The secret is in this response only.
curl -X POST http://localhost:8000/api/v1/api-keys \
-H "Authorization: Bearer $PRIVY_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Agent"}'{
"id": "6b1c…",
"name": "Agent",
"key_prefix": "stv_a1b2c3d4",
"revoked": false,
"created_at": "2026-11-10T12:00:00+00:00",
"last_used_at": null,
"secret": "stv_a1b2c3d4_…"
}400 with Maximum 5 active API keys when the account is already at the cap. Revoke one, then create again.
GET/api/v1/api-keys
Keys on the signed-in account, newest first, secrets omitted. Requires the session bearer. A revoked key stays in the list with revoked: true.
DELETE/api/v1/api-keys/{id}
Revoke one key on the signed-in account. Requires the session bearer. Later calls with that secret answer 401. 404 when the id is not on this account. The response is the key row with revoked: true.
GET/api/v1/locks/requirements
The minimum $SENTRIVA amount and the minimum days for each lock tier. Public.
{
"builder": { "min_amount": 1000, "min_days": 30 },
"pro": { "min_amount": 10000, "min_days": 90 },
"institutional": { "min_amount": 100000, "min_days": 180 }
}POST/api/v1/locks
Open a lock and raise the account tier. Requires the session bearer. tx_signature is optional until the $SENTRIVA contract is attached. Listing locks is GET /api/v1/locks on the same session.
{
"amount": 1000,
"lock_days": 30,
"tier_unlocked": "builder",
"tx_signature": null
}400 when the amount or the window is under the tier minimum. The detail string names the floor, for example Minimum 1,000 $SENTRIVA required for builder.
POST/api/v1/locks/{id}/unlock
End a lock early. Requires the session bearer. The account tier is recomputed from the locks that remain. A 400 means the lock is missing, belongs to another account, or is already unlocked.
GET/api/v1/users/me
The signed-in account. Requires the session bearer. PATCH /api/v1/users/me with { "display_name": "Ada" } updates the name and returns the same object.
{
"id": "6b1c…",
"email": "ada@example.com",
"wallet_address": "0xabc…",
"display_name": "Ada",
"api_tier": "builder",
"created_at": "2026-11-10T12:00:00+00:00"
}GET/api/health
Liveness, and which optional backends this process has configured. Public. Not rate shaped.
{
"status": "ok",
"privy_configured": false,
"dune_configured": true,
"desk_enabled": true
}