User Data

Endpoints for querying your profile, orders, trades, positions, fiat sessions, notifications, referrals, and rebates. All endpoints require authentication.

Profile

GET /v1/user/profile

Returns the authenticated user's profile including wallet addresses, display name, and account settings.

Response

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "email": "user@example.com",
  "display_name": "trader1",
  "wallet_address": "0xabc...",
  "linked_wallets": ["0xabc...", "0xdef..."],
  "role": "user",
  "created_at": "2024-01-15T10:30:00Z"
}

Orders (Enhanced)

GET /v1/user/orders/v2

Scope: read:account

Query your orders with advanced filters and pagination.

Query Parameters

ParameterTypeDescription
statusstringComma-separated: open,filled,cancelled
sidestringBUY or SELL
outcomeintOutcome index filter
sincestringISO 8601 timestamp (orders after this)
sortstringcreated_at or updated_at (default)
limitintMax results (default 50, max 200)
offsetintSkip N results

Response

{
  "orders": [
    {
      "id": "...",
      "order_hash": "0x...",
      "market_id": "...",
      "side": "BUY",
      "price": "0.50000000",
      "status": "open",
      "remaining": "1000000",
      "filled_amount": "0",
      "time_in_force": "GTC",
      "post_only": false,
      "created_at": "2024-03-01T00:00:00Z"
    }
  ],
  "total": 150,
  "limit": 50,
  "offset": 0
}

Trades (Enhanced)

GET /v1/user/trades/v2

Scope: read:account

Query your trade history with filters.

Query Parameters

ParameterTypeDescription
market_idUUIDFilter by market
sidestringBUY or SELL (taker side)
sincestringISO 8601 timestamp (trades after this)
limitintMax results (default 50, max 200)
offsetintSkip N results

Response

{
  "trades": [
    {
      "id": "...",
      "market_id": "...",
      "taker_order_id": "...",
      "maker_order_id": "...",
      "price": "0.50000000",
      "amount": "1000000",
      "taker_side": "BUY",
      "outcome_index": 0,
      "matched_at": "2024-03-01T12:30:00Z"
    }
  ],
  "total": 42,
  "limit": 50,
  "offset": 0
}

Positions

GET /v1/user/positions

Returns all positions for the authenticated user across all markets.

Response

[
  {
    "id": "...",
    "market_id": "...",
    "outcome_index": 0,
    "quantity": "5000000",
    "average_entry_price": "0.45000000",
    "realized_pnl": "0",
    "claimed": false
  }
]

Redeemable Positions

GET /v1/user/redeemable

Returns positions in resolved markets that can be claimed (redeemed for USDC).

Response

[
  {
    "market_id": "...",
    "outcome_index": 0,
    "quantity": "5000000",
    "payout": "5000000",
    "resolution": "YES",
    "resolved_at": "2024-03-15T18:00:00Z"
  }
]

Claim Winnings

POST /v1/user/positions/:marketId/claim

Initiates an on-chain redemption of winning positions in a resolved market. This triggers a transaction on Polygon to transfer USDC to your wallet.

Response

{
  "message": "claim initiated",
  "market_id": "...",
  "tx_hash": "0x..."
}

Mark Position Claimed

POST /v1/user/positions/:marketId/mark-claimed

Marks a position as claimed after an external on-chain redemption (e.g., if you called the contract directly).

Response

{
  "message": "position marked as claimed"
}

Fiat Sessions

List Sessions

GET /v1/user/fiat/sessions

Returns all on-ramp and off-ramp sessions for the authenticated user.

Get Session

GET /v1/user/fiat/sessions/:id

Returns details of a specific fiat session.

Create On-Ramp Session

POST /v1/user/fiat/onramp/sessions

Creates a new fiat-to-crypto on-ramp session (deposit USDC).

Complete On-Ramp Session

POST /v1/user/fiat/onramp/sessions/:id/complete

Marks an on-ramp session as complete after payment confirmation.

Create Off-Ramp Session

POST /v1/user/fiat/offramp/sessions

Creates a new crypto-to-fiat off-ramp session (withdraw to bank).

Complete Off-Ramp Session

POST /v1/user/fiat/offramp/sessions/:id/complete

Marks an off-ramp session as complete once the on-chain USDC transfer settles. Returns { session_id, status }.

Refresh Launch Token

POST /v1/user/fiat/onramp/sessions/:id/launch-token
POST /v1/user/fiat/offramp/sessions/:id/launch-token

Mints a fresh Coinbase sessionToken + provider launch URL for an existing session. Use when the original launch URL has expired and the user wants to resume the flow without creating a brand-new session.

{
  "session_id": "9f3c…",
  "status": "initiated",
  "launch_url": "https://pay.coinbase.com/buy/select-asset?sessionToken=…"
}

Get Off-Ramp Transaction

GET /v1/user/fiat/offramp/sessions/:id/transaction

Polls Coinbase for the off-ramp transaction details. While Coinbase hasn't observed a transaction yet, returns the pending shape:

{
  "session_id": "9f3c…",
  "status": "awaiting_quote",
  "message": "Awaiting transaction"
}

Once Coinbase issues the transaction, returns the observed shape — the frontend reads to_address + sell_amount to drive the on-chain USDC transfer:

{
  "session_id": "9f3c…",
  "status": "created",
  "to_address": "0xCoinbase…",
  "asset": "USDC",
  "network": "polygon",
  "sell_amount": { "value": "100.00", "currency": "USDC" },
  "tx_hash": "",
  "created_at": "2026-05-22T10:00:00Z"
}

Report Off-Ramp Tx Hash

POST /v1/user/fiat/offramp/sessions/:id/tx

After completing the on-chain USDC transfer to the Coinbase address, report the tx hash so 4rho can track settlement. Body: { "tx_hash": "0x…" }. Response echoes the hash for the FE to confirm:

{
  "session_id": "9f3c…",
  "status": "tx_reported",
  "tx_hash": "0x…"
}

Rebates

GET /v1/user/rebates

Returns trading rebate history and balances for the authenticated user.

Response

{
  "total_rebates": "15000000",
  "pending_rebates": "5000000",
  "credited_rebates": "10000000",
  "history": [
    {
      "id": "...",
      "amount": "5000000",
      "reason": "maker_rebate",
      "created_at": "2024-03-01T12:00:00Z"
    }
  ]
}

Referrals

Get Referral Info

GET /v1/user/referral

Returns the user's referral code and referral status.

Response

{
  "referral_code": "ABC123",
  "referrals_count": 5,
  "is_referred": true,
  "referred_by": "XYZ789"
}

Get Referral Earnings

GET /v1/user/referral/earnings

Returns earnings generated from referrals.

Applying a Referral Code

Referrals are signup-only. The redemption path is the optional referral_code field on POST /v1/auth/privy (or a 4rho_referral httpOnly cookie set by visiting /refer/[code]); both attach the referral atomically inside the signup handler. There is no post-signup POST /v1/user/referral/redeem endpoint — it was retired with the #3778 Privy-only migration. A user can only ever be linked to one referrer.

Notifications

List Notifications

GET /v1/notifications

Returns notifications for the authenticated user (order fills, market resolutions, etc.) in (created_at DESC, id DESC) order. The response also carries the user's TOTAL unread count so a bell-badge consumer doesn't need a second round-trip on each inbox open.

Query Parameters

ParameterTypeDescription
cursorstringOpaque pagination token returned as next_cursor on a prior response.
limitintPage size (default 20, max 100).

Response

{
  "items": [
    {
      "id": "...",
      "type": "order_filled",
      "title": "Order filled",
      "body": "Your BUY order was filled at 0.52",
      "read_at": null,
      "created_at": "2024-03-01T14:30:00Z"
    }
  ],
  "next_cursor": null,
  "unread_count": 3
}

Unread Count

GET /v1/notifications/unread-count

Lightweight count-only endpoint for bell-badge polling (Redis-cached, no list payload).

{ "unread_count": 3 }

Mark Notification Read

POST /v1/notifications/:id/read

Marks a single notification as read.

Mark All Notifications Read

POST /v1/notifications/mark-all-read

Marks every unread notification as read. Response includes the number of rows flipped:

{ "marked": 3 }

Achievements

GET /v1/user/achievements

Scope: read:account

Returns the achievement chips granted to the authenticated user. New eligibility is computed and granted on each call — there's no background worker, so the response is always up to date.

Response

[
  { "id": "joined",             "granted_at": "2026-01-15T10:30:00Z" },
  { "id": "first_trade",        "granted_at": "2026-01-15T11:02:14Z" },
  { "id": "markets_traded_10",  "granted_at": "2026-02-03T18:44:00Z" },
  { "id": "markets_traded_100", "granted_at": "2026-04-21T09:12:00Z" },
  { "id": "sharp",              "granted_at": "2026-05-10T22:00:00Z" },
  { "id": "top_5pct_nfl",       "granted_at": "2026-05-17T14:00:00Z" }
]

Possible id values: joined, first_trade, markets_traded_10, markets_traded_100, sharp (≥ 55% win rate over ≥ 20 closed positions), top_5pct_nfl (top 5% of NFL traders by P&L over a 30d window, min 5 closed positions in that category).

The endpoint always returns a JSON array — [] for users with no chips, never null.

Rolling 24h P&L

GET /v1/user/portfolio/pnl-24h

Scope: read:account

Returns the user's available USDC balance plus their rolling 24-hour P&L delta computed from cached position marks. Powers the Pro mode StatusBar. Response is memoized per-user for 30s.

Response

{
  "available_balance_usdc": "1245.67",
  "pnl_24h_usdc": "+42.31",
  "pnl_24h_pct": 3.51,
  "computed_at": "2026-05-22T10:00:00Z"
}

pnl_24h_usdc is signed ("+42.31" / "-12.04") and rounded to 2dp as a string to avoid float-precision drift in the UI. pnl_24h_pct is 0 when the baseline (positions at now - 24h) is zero, never Inf or NaN.

Responsible Gaming

Self-imposed reality-check intervals, session-timeout warnings, and trading timeouts. The timeout endpoints back the cooldown self-service UI; when an active timeout exists every order-placement and amend route returns 423 Locked + Retry-After + code TRADING_TIMEOUT_ACTIVE until it expires.

Get Preferences

GET /v1/user/rg/preferences

Scope: read:account

{
  "reality_check_interval_minutes": 30,
  "session_timeout_minutes": 120
}

Both fields are nullable — null means the feature is off for the user.

Update Preferences

PUT /v1/user/rg/preferences

Scope: manage:account

Allowed values:

  • reality_check_interval_minutes{15, 30, 60, 120} or null
  • session_timeout_minutes{60, 120, 240, 480} or null
{
  "reality_check_interval_minutes": 60,
  "session_timeout_minutes": null
}

Returns the same shape as GET. Out-of-range values return 400 INVALID_INPUT.

Get Active Trading Timeout

GET /v1/user/rg/timeout

Scope: read:account

Returns the active trading-timeout row, or literal null when none is active.

{
  "id": "9f3c…",
  "started_at": "2026-05-22T10:00:00Z",
  "ends_at":    "2026-05-23T10:00:00Z",
  "duration_label": "24h"
}

Start a Trading Timeout

POST /v1/user/rg/timeout

Scope: manage:account

Body:

{ "duration_label": "24h" }

duration_label"24h" | "7d" | "30d" | "custom". When "custom", supply custom_hours (integer hours). Returns 201 with the timeout row, or 409 TIMEOUT_ALREADY_ACTIVE if one is still in effect.

Cancel the Active Timeout

POST /v1/user/rg/timeout/cancel

Scope: manage:account

Sets cancelled_at = NOW() on the active row. Returns 404 NO_ACTIVE_TIMEOUT when nothing is active.

Legacy Endpoints

The original order and trade endpoints are still available at:

  • GET /v1/user/orders — Basic order list (no filters)
  • GET /v1/user/trades — Basic trade list (no filters)

Use the /v2 versions for advanced filtering and pagination.