---
title: "Open positions only (leaner /pnl slice)"
method: GET
path: "/wallet/{address}/positions"
tags: ["Wallet"]
---

# Open positions only (leaner /pnl slice)

`GET /wallet/{address}/positions`

## Request

- Base URL: `https://madeonsol.com/api/v1`
- URL: `https://madeonsol.com/api/v1/wallet/{address}/positions`
- Auth: HTTP bearer

Open lots only — same data as `/pnl` `open_positions[]` but without the summary, curve, or closed-positions payload. Shares the `wallet_analyses` cache: if you've recently called `/pnl` for this wallet, this call is an immediate cache hit. PRO+.

## Path parameters

- `address` string, required

## Response `200`

OK

- object
  - `address` string
  - `completeness` 'complete' | 'incomplete': Realized-PnL completeness. incomplete when at least one sell in the response has proceeds that went to another wallet (price_status proceeds_unattributed, migration 508): realized PnL and ROI then cover the known basis only, and the basis those sells consumed is reported as unknown_realized_cost_basis_sol, never as 0 proceeds or a loss. This is realized-PnL completeness. It is NOT `coverage.completeness` (source-coverage verification: verified / not_verified / ...), which says whether the trade capture for this subject is proven complete.
  - `incomplete_reasons` string[]: Empty when complete. /wallet/{address} and /tokens/{mint}/top-traders emit only proceeds_unattributed; unknown_consideration is shared with the FIFO routes (/pnl, /positions).
  - `summary` object: Realized-PnL completeness of the wallet (same values as /pnl summary).
    - `unknown_realized_cost_basis_sol` number: Migration 508: cost basis consumed by sells whose proceeds went to another wallet. NOT in realized_pnl_sol or ROI (neither a loss nor a gain). 0 when complete.
  - `positions` WalletOpenPosition[]
    - `token_mint` string
    - `token_amount` number
    - `cost_basis_sol` number
    - `avg_entry_price_sol` number
    - `current_price_sol` number, nullable: Live from market-cap tracker. null if the mint has no current price (e.g. delisted).
    - `current_value_sol` number, nullable
    - `unrealized_sol` number, nullable
    - `unrealized_pct` number, nullable
    - `first_buy_at` string, date-time, nullable
    - `buys_in_position` integer
    - `position_basis` 'swap_derived': Positions are FIFO-open DEX buys from token_trades (trade position), not a proven holding.
    - `holding_status` 'verified' | 'unverified'
    - `holding_unverified_reason` 'not_checked' | 'no_fresh_snapshot' | 'snapshot_unreadable' | 'snapshot_older_than_trades' | 'decimals_mismatch' | 'balance_invalid', nullable
    - `cost_basis_status` 'known' | 'partial': partial = a verified balance exceeds the trade position; the excess arrived without a swap and has no cost basis.
    - `holding` object, nullable
      - `status` 'HELD' | 'PARTIALLY_REDUCED' | 'TRANSFERRED_OR_DISPOSED' | 'EXTERNAL_INFLOW'
      - `onchain_balance` number
      - `held_known_amount` number
      - `external_inflow_amount` number
      - `cost_basis_held_sol` number
      - `unrealized_known_sol` number, nullable
      - `cost_basis_not_held_sol` number: Cost of FIFO lots no longer in the wallet (transfer out / unindexed disposal). Outcome unknown: neither realized nor unrealized.
      - `verified_at` string, date-time
      - `source` 'wallet_holdings_cache'
  - `holding_check` object: COV-21: summary of the proven-holding check over the returned open positions (SOLANA_POSITION_HOLDING; mode off = every position unverified/not_checked).
    - `mode` 'off' | 'cache'
    - `source` 'wallet_holdings_cache', nullable
    - `verified_at` string, date-time, nullable
    - `verified` integer
    - `unverified` integer
    - `held` integer
    - `partially_reduced` integer
    - `transferred_or_disposed` integer
    - `external_inflow` integer
    - `note` string
  - `cache_hit` boolean
  - `computed_at` string, date-time, nullable
  - `cache_age_seconds` integer: Cache hits only — seconds since computed_at
  - `cache_validation` 'head_checked' | 'unverified': Cache hits only (audit F16): head_checked = the wallet has no persisted trade newer than the analysis watermark, so the cached analysis is current; unverified = the trade-head probe failed and the unexpired cache was served flagged. A hit whose wallet traded after the watermark is never served — it is recomputed.
  - `cache_invalidated` 'new_activity': Present on a recompute that happened because a cached analysis existed but the wallet traded after it
  - `ttl_seconds` integer, nullable

## Other responses

- `400`: Invalid wallet address
- `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/96ce84657f55?raw)
