---
title: "Batch per-mint snapshot (up to 50 mints)"
method: POST
path: "/token/batch"
tags: ["Tokens"]
---

# Batch per-mint snapshot (up to 50 mints)

`POST /token/batch`

## Request

- Base URL: `https://madeonsol.com/api/v1`
- URL: `https://madeonsol.com/api/v1/token/batch`
- Auth: HTTP bearer

Same per-mint shape as `GET /token/{mint}` but in one round-trip. DB queries batched with `IN (...)`; dex-stream + RPC supply fan-outs run in parallel. 10–20× cheaper than N sequential single-mint calls.

## Request body

- object
  - `mints` union, required: Up to 50 Solana token mints — a comma-separated string or a JSON array
    - string
    - string[]

## Response `200`

OK. Response contains `tokens[]` with the same per-mint shape as `/token/{mint}` (including `launchpad`).

- object
  - `tokens` object[]: One entry per unique requested mint. Same price-freshness (price_source/price_observed_at/price_age_seconds/price_is_stale), creator-identity and KOL-aggregate contract as GET /token/{mint}. Unlike it, does not include name/symbol/image_url/socials/description/twitter_reuse/early_buyer_exit or a per-token data_status (failed blocks are listed once in top-level degraded_fields).
    - `mint` string
    - `price_usd` number, nullable
    - `price_sol` number, nullable
    - `market_cap` number, nullable
    - `vwap_price_usd` number, nullable
    - `vwap_price_sol` number, nullable
    - `vwap_status` 'ok' | 'unavailable' | 'no_price_reference' | 'implausible_vs_price': Why vwap_price_usd / vwap_price_sol are null, or ok. A 15-minute VWAP more than 1000x away from the price it is shipped with is withheld (implausible_vs_price) instead of served (2026-10-15, #652).
    - `fdv_usd` number, nullable
    - `liquidity_usd` number, nullable
    - `primary_dex` string, nullable
    - `primary_pool_address` string, nullable
    - `is_token_2022` boolean, nullable: null = no mc-tracker observation (unknown)
    - `transfer_fee_bps` integer, nullable: null = unknown — NOT "no fee"
    - `mint_authority_revoked` boolean, nullable
    - `freeze_authority_revoked` boolean, nullable
    - `burn_detected` boolean, nullable: DEPRECATED meaning: `true` when the token MINT's on-chain supply decreased (a token burn, measured by mc-tracker's supply-drift monitor). It is NOT LP-burn evidence and never means liquidity cannot be removed. null = no mc-tracker observation (unknown).
    - `token_supply_burn_detected` boolean, nullable: The mint's supply decreased (token burn). Not LP evidence.
    - `lp_burn_status` 'verified' | 'not_verified' | 'unknown': LP burn/lock evidence: `verified` only with measured LP-token custody (lp_burnt_pct ≥ 99). No Solana writer populates LP custody yet, so today every token is `unknown`. A token-supply burn is never promoted to an LP claim.
    - `launchpad` 'pumpfun' | 'launchlab' | 'bags' | 'null', nullable
    - `liquidity_to_mc_ratio` number, nullable
    - `volume_24h_usd` number, nullable
    - `volume_24h_sol` number, nullable
    - `trades_24h` integer, nullable
    - `price_source` 'mc_tracker' | 'dex_stream' | 'null', nullable: Which pipeline produced price_usd/price_sol; null when there is no price
    - `price_observed_at` string, date-time, nullable: Last trade observed BY THE SELECTED price source — the price's own age anchor
    - `price_age_seconds` integer, nullable: Seconds since price_observed_at (null when the source carries no timestamp)
    - `price_is_stale` boolean, nullable: true when price_age_seconds > 900; null (unknown) when a price exists but its source has no observation time — never false without an observation; false when there is no price
    - `last_trade_at` string, date-time, nullable: Newest trade observed by ANY source (mc-tracker or dex-stream). Not a price-age anchor — use price_observed_at
    - `first_seen_at` string, date-time, nullable
    - `age_seconds` integer, nullable
    - `is_blacklisted` boolean, nullable: null = the blacklist read failed (listed in degraded_fields) — never "not blacklisted"
    - `blacklist_category` string, nullable
    - `launch_cohort_sol` number, nullable
    - `launch_cohort_size` integer, nullable: null = the cohort read failed; 0 = no cohort rows
    - `deployer` object, nullable: Creator + live reputation. Resolved from `deployer_tokens` (bonded) or `pending_deploys` (not yet bonded — ~98% of fresh launches). null ⇒ read `deployer_identity` for why (unknown creator vs failed lookup). `history_status` other than `established` means the bonding rate is not a track record yet (a first launch is "0 of 1", not a serial dumper).
      - `wallet` string
      - `address` string: Same value as wallet (contract name)
      - `tier` string
      - `bonding_rate` number
      - `total_deployed` integer
      - `total_bonded` integer
      - `recent_bond_rate` number
      - `identity_status` 'resolved'
      - `identity_source` 'deployer_tokens' | 'pending_deploys'
      - `history_status` 'new_in_our_index' | 'limited_history' | 'reputation_pending' | 'established'
      - `first_seen_at` string, date-time, nullable: First deploy OBSERVED in our capture — not the wallet's first launch anywhere
      - `observed_launch_count` integer
      - `resolved_outcome_count` integer: Resolved outcomes (bonds + dumps) we hold for this creator
      - `stats_computed_at` string, date-time, nullable
    - `deployer_identity` object: Always present. Explains a null `deployer`: `unknown` = both lookups succeeded and found no creator; `lookup_failed` = a lookup errored (retry — NOT "no deployer").
      - `identity_status` 'resolved' | 'unknown' | 'lookup_failed'
      - `history_status` 'new_in_our_index' | 'limited_history' | 'reputation_pending' | 'established' | 'null', nullable
      - `address` string, nullable
      - `source` 'deployer_tokens' | 'pending_deploys' | 'null', nullable
    - `kol_activity` object: Per-mint complete 7-day aggregate (no shared row budget across the batch). complete=false = the aggregate read failed (listed in degraded_fields): figures are null, never zero.
      - `buying_kols` integer, nullable
      - `selling_kols` integer, nullable
      - `net_flow_sol` number, nullable
      - `signal` 'accumulating' | 'distributing' | 'neutral' | 'null', nullable
      - `top_buyers` object[]: Largest KOL buyers by SOL bought over the whole window (≤ 10, complete ranking)
        - `name` string
        - `wallet` string: ULTRA only
        - `sol_amount` number
      - `unique_kols` integer
      - `buys` integer
      - `sells` integer
      - `buy_sol` number
      - `sell_sol` number
      - `last_trade_at` string, date-time, nullable
      - `window_hours` integer
      - `window_start` string, date-time
      - `complete` boolean
    - `mc_change_pct` object, nullable: Present only when history exists. Keys: 5m, 15m, 1h, 2h, 4h
    - `volume_usd` object, nullable: Present only when history exists. Keys: 5m, 15m, 1h, 2h, 4h
    - `mev_volume_pct` object, nullable: Present only when history exists. Keys: 5m, 15m, 1h, 2h, 4h
    - `history_age_seconds` integer, nullable: Present only when history exists
  - `count` integer
  - `as_of` string, date-time: Response assembly time — not an observation time
  - `degraded_fields` string[]: Blocks whose lookup failed for this call; their per-token fields are null (unknown), never an empty-state default

## Other responses

- `400`: Invalid body or mint
- `401`: Missing or invalid API key, or an expired free-tier key. Authentication-stage error: no `X-RateLimit-*` headers, no `_rid`.
- `403`: Forbidden. One of: the key's tier is below this endpoint's or parameter's gate (`tier_required` with an `upgrade` block); the public demo key used outside its allow-list (`demo_key_endpoint_not_allowed`); an invalid or revoked key; an origin-restricted key called from a disallowed origin (`code` `origin_not_allowed` / `origin_required`). `X-RateLimit-*` headers are present only on `tier_required`.
- `429`: Too many requests. Every 429 carries `Retry-After`. A quota 429 (`rate_limit_exceeded`, with `X-RateLimit-*`), a heavy-query concurrency 429 (`error_kind` `concurrency`/`capacity`), or the per-IP authentication-failure block (30 failed auths/min; plain `error` + `retry_after_seconds`, no `X-RateLimit-*`).
- `500`: Unexpected server error from the route (`Internal server error`, with `_rid`). Safe to retry with backoff. A failed API-key lookup is a 503 `auth_backend_unavailable`, never a 500.
- `503`: The API-key or subscription lookup could not be answered (authentication stage). Retryable and not your fault: it is never counted as a failed authentication. Honour `Retry-After`; no `X-RateLimit-*`, no `_rid`.

---

[API](https://skmtc.dev/madeonsol/apis/madeonsol-api.md) · [All operations](https://skmtc.dev/madeonsol/apis/madeonsol-api/llms.txt) · [OpenAPI document](https://skmtc.dev/madeonsol/apis/madeonsol-api/revisions/d9531a28d83b?raw)
