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
| Parameter | Type | Description |
|---|---|---|
status | string | Comma-separated: open,filled,cancelled |
side | string | BUY or SELL |
outcome | int | Outcome index filter |
since | string | ISO 8601 timestamp (orders after this) |
sort | string | created_at or updated_at (default) |
limit | int | Max results (default 50, max 200) |
offset | int | Skip 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
| Parameter | Type | Description |
|---|---|---|
market_id | UUID | Filter by market |
side | string | BUY or SELL (taker side) |
since | string | ISO 8601 timestamp (trades after this) |
limit | int | Max results (default 50, max 200) |
offset | int | Skip 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
| Parameter | Type | Description |
|---|---|---|
cursor | string | Opaque pagination token returned as next_cursor on a prior response. |
limit | int | Page 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}ornullsession_timeout_minutes∈{60, 120, 240, 480}ornull
{
"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.