Sentriva

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.

Access follows a $SENTRIVA lock.

The tape and a token report are readable with no key. A key names the account, and the tier on that account is the highest active lock. Issue the key from the keys page after you open the app with email, MetaMask, Phantom, or OKX. The schedule is on pricing.

Quickstart

  1. 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.
  2. Read the tape. Filter by score, chain, launchpad, market cap, or pair age. One row is one token the desk is watching.
  3. Read the verdict. verdict is watch, alert, enter_candidate, or reject. final_score is 0–100. The full report adds reasons.
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".

Key header
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.

TierLockWindowCeiling
Explorer (free)——2 / s, burst 4
Builder1,000 $SENTRIVA30 days10 / s, burst 20
Pro10,000 $SENTRIVA90 days200 / s, burst 400
Enterprise100,000 $SENTRIVA180 days2,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.

Error body
{ "detail": "Invalid API key" }
StatusWhenWhat to do
400Empty resolve query, a lock below the tier minimum, or a sixth active key.Read detail and fix the request.
401The API key is missing, unknown, or revoked, or the session token does not verify.Check X-API-Key, or sign in again.
404Unknown token, unknown key id, or a lock that is not on this account.Check the path.
422A 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.
503The 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

NameInDescription
tabqueryall (default), alerts, or watchlists. Anything else is treated as all.
min_scorequeryNumber. Drop tape rows under this final_score. Default 0.
chainquerysolana, base, bsc, or robinhood. Omit for every chain.
launchpadqueryCase-insensitive exact display name, such as pump.fun, StonkFun, pons v2, or flap v2.
alert_onlyquerytrue keeps tape rows the scorer has already flagged.
stock_pairsquerytrue keeps pairs quoted in a stock asset.
has_linksquerytrue keeps rows with a website, X, Telegram, or Discord.
min_mcap, max_mcapqueryMarket cap bounds, in USD, as numbers.
min_age_min, max_age_minqueryPair 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.

200 · tape
{
  "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.

FieldTypeMeaning
idstringchain:address. This is the token id used everywhere else.
symbol, chain, addressstringDisplay symbol, chain slug, and the mint or contract.
verdictstringwatch, alert, enter_candidate, or reject.
final_scorenumber0–100. The number the tape sorts and filters on.
price, mcap, liq, vol1hnumber?USD. Null when the venue did not report it.
holdersinteger?Holder count. Falls back to the clustered unique-holder figure.
launchpadstring?Display name. Null when no origin was found.
dex_id, quotestring?Current pool DEX, and the quote symbol (SOL, WETH, STONK, …).
pair_age, pair_age_minstring?, number?How old the pair is. The string is 75m, 8.8h, 77d, 4.2mo, or years.
age_labelstring?How long since this snapshot was written, such as 12s.
graduatedboolean?The launchpad curve has completed.
stock_pairbooleanThe quote is a stock asset.
cluster_pctstring?Largest holder cluster, already formatted, such as 12%.
has_linksbooleanA website, X, Telegram, or Discord is on the profile.
dexscreener, fomostring?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.

200
{
  "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

NameInDescription
chainpathsolana, base, bsc, or robinhood. Lowercase.
addresspathMint 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
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
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

FieldTypeMeaning
id, desk, chain, address, symbolstringdesk is meme or defi.
pairAddressstring?The pool the market figures come from.
market.priceUsd, mcapUsd, liqUsdnumber?USD.
market.vol1h, vol24hnumber?USD volume.
market.buys1h, sells1hinteger?Trade counts over the last hour.
market.priceChange1h, priceChange24hnumber?Already percent. −23.3 means −23.3%, not a fraction.
market.pairAgeMinnumber?Pair age in minutes at the last write.
market.pairCreatedAtinteger?Pair creation time, unix milliseconds.
updatedAtstringISO time of this snapshot.

Venue, profile, holders

FieldTypeMeaning
venue.dexIdstring?Pool DEX, such as raydium, uniswap, or meteora.
venue.launchpadstring?Stored origin. Empty when inference found nothing at ingest.
venue.launchpadGraduatedboolean?The curve completed.
venue.holdersinteger?Holder count.
venue.quoteSymbolstring?SOL, WETH, STONK, and so on.
venue.labelsstring[]Venue tags, such as a pool version.
profile.imageUrl, website, twitter, telegram, discordstring?Socials. twitter is the X URL.
cluster.decentralizationScorenumber?0–100. Higher is more spread out.
cluster.topClusterPct, top10Pct, snipersPct, insidersPct, devPct, bundlersPctnumber?Fractions. 0.38 is 38%.
cluster.topHolders[]object[]owner, pct (fraction), tag, insider, isContract. Up to the holders the enricher kept.
reasonsstring[]Why the score landed where it did. Cleared and rewritten on every rescore.
snap.scores and a holder
{
  "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.

verdictReading
enter_candidateThe score cleared the desk line and no kill condition fired.
alertWorth a look. A condition fired, or the score is close.
watchIndexed, and not yet a candidate.
rejectA 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.

PasteResult
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 longerkind address, chain solana.
A bare 0x contract, 42 characterskind address. The chain is the one with the deepest pool for that contract.
An address with no market, or any other paste400. 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.

200 · mint
{ "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.

200 · one pack
{
  "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.

200
{
  "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.

Request
curl -X POST http://localhost:8000/api/v1/api-keys \
  -H "Authorization: Bearer $PRIVY_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"Agent"}'
200 · secret shown once
{
  "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.

200
{
  "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.

Request
{
  "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.

200
{
  "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.

200
{
  "status": "ok",
  "privy_configured": false,
  "dune_configured": true,
  "desk_enabled": true
}