---
title: "Newest token locks & vesting contracts (all mints)"
method: GET
path: "/tokens/locks"
tags: ["Tokens"]
---

# Newest token locks & vesting contracts (all mints)

`GET /tokens/locks`

## Request

- Base URL: `https://madeonsol.com/api/v1`
- URL: `https://madeonsol.com/api/v1/tokens/locks`
- Auth: HTTP bearer

Cross-token feed of **new lock / vesting contracts** — who just locked tokens, of what mint, how much, until when — newest first, from Streamflow, Jupiter Lock and Bonfida vesting. Poll with 'since=' (cursor = 'pagination.next_since') or subscribe to the **'token:locks'** WebSocket channel for a push the moment the contract lands on-chain. Filters: 'mint', 'sender', 'recipient', 'program', 'kind', 'status', 'min_usd', 'min_pct_of_supply' (the last three are post-filters served by a bounded scan — see 'pagination.scan_truncated'). Backfilled Jupiter Lock rows have no on-chain creation time and are excluded by default ('include_estimated=1' to include). PRO+.

## Query parameters

- `since` string, date-time
- `before` string, date-time
- `cursor` string
- `mint` string
- `sender` string
- `recipient` string
- `program` 'streamflow' | 'jupiter_lock' | 'bonfida_vesting' | 'smithii_vesting' | 'sablier_lockup'
- `kind` 'lock' | 'vesting'
- `status` 'active' | 'completed' | 'cancelled' | 'closed'
- `min_usd` number
- `min_pct_of_supply` number
- `include_estimated` '1' | '0' | 'true' | 'false'
- `limit` integer

## Response `200`

OK

- object
  - `locks` object[]
    - `lock_account` string: The contract account (Streamflow stream / Jupiter VestingEscrow / Bonfida vesting account)
    - `program` 'streamflow' | 'jupiter_lock' | 'bonfida_vesting' | 'smithii_vesting' | 'sablier_lockup'
    - `provider` object: Who runs the lock contract, and how sure we are. identity: verified = a known provider deployment (by contract address); compatible = only the event shape (ABI) matches a known provider, the operator is NOT identified; unverified = unknown. id/website_url are null unless verified. lock_url is a per-lock page on the provider's site, emitted ONLY where the URL format is proven (none is proven for Streamflow / Jupiter Lock / Bonfida today, so it is always null); otherwise null (never guessed).
      - `id` string, nullable
      - `name` string, nullable
      - `identity` 'verified' | 'compatible' | 'unverified'
      - `compatible_with` string, nullable
      - `website_url` string, nullable
      - `lock_url` string, nullable
    - `explorer` object: Independent evidence on Solana Explorer.
      - `lock_account_url` string, nullable
      - `creation_tx_url` string, nullable
    - `price_usd` number, nullable: Token price used for the *_usd fields (null when unknown or guarded as phantom).
    - `seconds_until_end` integer, nullable: Seconds until fully unlocked (>= 0). 0 once completed; null when there is no end date (perpetual) or the lock was cancelled/closed.
    - `seconds_until_next_unlock` integer, nullable: Seconds until next_unlock.at (>= 0); null when there is no next unlock.
    - `kind` 'lock' | 'vesting': lock = whole amount at one date; vesting = cliff and/or periodic release
    - `status` 'active' | 'completed' | 'cancelled' | 'closed': Derived at request time
    - `mint` string
    - `sender` string, nullable: Creator / locker (Bonfida has none on-chain)
    - `recipient` string, nullable: Beneficiary. sablier_lockup: the CURRENT proven holder of the stream NFT only; null when the holder is not currently proven (see holder_status), never the last proven holder. smithii_vesting: the authority for single-receiver locks, null for merkle receivers (off-chain).
    - `holder_status` 'current' | 'lost_proof' | 'never_proven' | 'burned' | 'anomaly' | 'null', nullable: sablier_lockup only (null for other programs): current = recipient proven by the latest read; lost_proof = recipient unknown, last_proven_holder is evidence; never_proven; burned = stream NFT burned, no recipient; anomaly = fail closed.
    - `last_proven_holder` string, nullable: sablier_lockup only: the last holder a verifying read proved (provenance, NOT the current recipient unless holder_status is current).
    - `holder_proven_at_slot` integer, nullable: sablier_lockup only: slot of the read that last proved last_proven_holder.
    - `name` string, nullable
    - `amount_raw` string
    - `amount` number, nullable
    - `amount_usd` number, nullable
    - `amount_pct_of_supply` number, nullable
    - `locked_raw` string: Still locked right now (amount − unlocked-so-far); 0 unless active
    - `locked` number, nullable
    - `locked_usd` number, nullable
    - `locked_pct_of_supply` number, nullable
    - `unlocked_raw` string
    - `unlocked` number, nullable
    - `withdrawn_raw` string, nullable: Claimed so far; null when the program does not expose it (withdrawn_tracked false: smithii_vesting)
    - `withdrawn` number, nullable
    - `withdrawn_tracked` boolean: false = withdrawn/claimable are unknown (null), not zero
    - `claimable_raw` string, nullable: Unlocked but not yet withdrawn; null when withdrawn is not tracked
    - `claimable` number, nullable
    - `start_at` string, date-time, nullable
    - `cliff_at` string, date-time, nullable
    - `end_at` string, date-time, nullable: Fully unlocked at; null = perpetual/no schedule
    - `period_seconds` integer, nullable
    - `continuous` boolean: period < 1h (per-second stream)
    - `amount_per_period_raw` string, nullable
    - `amount_per_period` number, nullable
    - `cliff_amount_raw` string, nullable
    - `cliff_amount` number, nullable
    - `perpetual` boolean
    - `schedule` object[]: Bonfida token-vesting only (absent on Streamflow / Jupiter Lock): the tranche list, each { release_at, amount_raw } with amount_raw a base-unit string.
      - `release_at` string, date-time, nullable
      - `amount_raw` string
    - `next_unlock` object, nullable
      - `at` string, date-time
      - `kind` 'cliff' | 'period' | 'final' | 'tranche'
      - `amount_raw` string
      - `amount` number, nullable
      - `amount_usd` number, nullable
    - `cancelable_by_sender` boolean, nullable: The locker can cancel — funds are locked against the recipient, not the locker
    - `cancelable_by_recipient` boolean, nullable
    - `transferable` boolean, nullable
    - `can_topup` boolean, nullable
    - `cancelled_at` string, date-time, nullable
    - `created_at` string, date-time, nullable
    - `created_at_estimated` boolean: Backfilled row with no on-chain creation time (Jupiter Lock)
    - `tx_signature` string, nullable
    - `token` object
      - `symbol` string, nullable
      - `name` string, nullable
      - `decimals` integer, nullable
      - `price_usd` number, nullable
      - `market_cap_usd` number, nullable
  - `pagination` object
    - `limit` integer
    - `count` integer
    - `has_more` boolean: false ONLY when the feed ran out — never inferred from a short post-filtered page
    - `next_since` string, date-time, nullable
    - `next_before` string, date-time, nullable: LEGACY — prefer next_cursor
    - `next_cursor` string, nullable: Pass as cursor= to page back; points past the last row CONSUMED, so a filtered page that matched nothing still advances. null = end of feed
    - `post_filtered` boolean
    - `scanned` integer: Candidate rows examined
    - `scan_truncated` boolean: The per-request scan budget ran out before `limit` matches — more matches MAY exist past the cursor/offset
    - `scan_budget` integer
  - `degraded_fields` string[]: mint_facts:<table> — a per-mint enrichment read (decimals/price/supply) failed; the affected usd/ui/pct values are null (unknown) and the min_usd / min_pct_of_supply post-filters could not be applied to those rows
  - `stream` object: WebSocket pointer for the token:locks channel
  - `meta` object

## Other responses

- `400`: Invalid query parameters
- `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`: Temporarily unavailable — a Postgres statement timeout while the dataset is under load or maintenance (`statement_timeout`, with `X-RateLimit-*` and `_rid`), or the API-key lookup itself unavailable (`auth_backend_unavailable`, authentication stage: no `X-RateLimit-*`, no `_rid`, not counted as a failed authentication). Always carries `Retry-After`; honour it.

---

[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/96ce84657f55?raw)
