{
    "openapi": "3.1.0",
    "info": {
        "title": "TopWallets Public API",
        "version": "1.0.0",
        "description": "Public REST API for topwallets.ai: token scans with their ~300 top wallets, individual wallet scans, and cross-token wallet correlation.\n\nAll endpoints require `Authorization: Bearer <api_key>`. Responses use a single envelope: `{ data, meta?, request_id }` on success, `{ error: { code, message, details? }, request_id, documentation_url }` on failure.",
        "contact": {
            "url": "https://www.topwallets.ai/docs/api"
        }
    },
    "servers": [
        {
            "url": "https://www.topwallets.ai"
        }
    ],
    "security": [
        {
            "bearerAuth": []
        }
    ],
    "paths": {
        "/api/v1/tokens/{address}/scans": {
            "post": {
                "operationId": "createTokenScan",
                "summary": "Trigger a token scan (async job)",
                "description": "Starts the asynchronous pipeline that fetches the token's top traders (~300 wallets), enqueues each wallet for PnL analysis and persists everything. Returns **202** with a `scan_id` immediately.\n\nLifecycle: POST here → poll `GET /scans/{scanId}` every `Retry-After` seconds (typical scan: 2–5 minutes) until `status` is `completed`, `failed`, or `stalled: true` → read `GET /tokens/{address}/top-wallets`.\n\nIdempotency: if a scan for this token is already running (progress updated within the last 15 minutes), the same `scan_id` is returned with `reused: true` — nothing is re-enqueued and no quota is consumed. A scan reporting `stalled: true` has left that window, so re-POSTing starts a fresh scan.\n\nPartial results are readable mid-scan via the result endpoint.",
                "parameters": [
                    {
                        "$ref": "#/components/parameters/TokenAddress"
                    }
                ],
                "requestBody": {
                    "required": false,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "additionalProperties": true,
                                "description": "No body fields are read; an empty body or {} is accepted."
                            }
                        }
                    }
                },
                "responses": {
                    "202": {
                        "description": "Scan accepted (fresh or reused). `Location` points to the status endpoint; poll it every `Retry-After` seconds.",
                        "headers": {
                            "Location": {
                                "schema": {
                                    "type": "string"
                                },
                                "description": "Status URL: /api/v1/scans/{scanId}"
                            },
                            "Retry-After": {
                                "schema": {
                                    "type": "string"
                                },
                                "description": "Suggested polling interval in seconds"
                            },
                            "X-Request-Id": {
                                "$ref": "#/components/headers/XRequestId"
                            },
                            "X-RateLimit-Limit": {
                                "$ref": "#/components/headers/XRateLimitLimit"
                            },
                            "X-RateLimit-Remaining": {
                                "$ref": "#/components/headers/XRateLimitRemaining"
                            },
                            "X-RateLimit-Reset": {
                                "$ref": "#/components/headers/XRateLimitReset"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": ["data", "request_id"],
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/TokenScanTriggerData"
                                        },
                                        "request_id": {
                                            "type": "string"
                                        }
                                    }
                                },
                                "example": {
                                    "data": {
                                        "scan_id": "wallet_processing_a1b2c3d4",
                                        "token_address": "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263",
                                        "status": "processing",
                                        "reused": false,
                                        "is_update": false,
                                        "links": {
                                            "status": "/api/v1/scans/wallet_processing_a1b2c3d4",
                                            "result": "/api/v1/tokens/DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263/top-wallets"
                                        }
                                    },
                                    "request_id": "9f1c2e9a-7b1d-4f6a-9c1e-2a3b4c5d6e7f"
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/ValidationError"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalError"
                    },
                    "502": {
                        "$ref": "#/components/responses/UpstreamError"
                    }
                }
            }
        },
        "/api/v1/scans/{scanId}": {
            "get": {
                "operationId": "getTokenScan",
                "summary": "Poll the status of a token scan",
                "description": "Returns live progress for a scan started with `createTokenScan`. While `status` is `processing` and the scan is not stalled, a `Retry-After` header suggests the next poll. `stalled: true` means no progress for 20+ minutes — it is safe to trigger a fresh scan.",
                "parameters": [
                    {
                        "name": "scanId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "pattern": "^wallet_processing_[0-9a-f]{8}$"
                        },
                        "description": "The scan_id returned by createTokenScan"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Current scan status",
                        "headers": {
                            "Retry-After": {
                                "schema": {
                                    "type": "string"
                                },
                                "description": "Present while processing and not stalled"
                            },
                            "X-Request-Id": {
                                "$ref": "#/components/headers/XRequestId"
                            },
                            "X-RateLimit-Limit": {
                                "$ref": "#/components/headers/XRateLimitLimit"
                            },
                            "X-RateLimit-Remaining": {
                                "$ref": "#/components/headers/XRateLimitRemaining"
                            },
                            "X-RateLimit-Reset": {
                                "$ref": "#/components/headers/XRateLimitReset"
                            }
                        },
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": ["data", "request_id"],
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/ScanStatusData"
                                        },
                                        "request_id": {
                                            "type": "string"
                                        }
                                    }
                                },
                                "example": {
                                    "data": {
                                        "scan_id": "wallet_processing_a1b2c3d4",
                                        "token_address": "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263",
                                        "status": "processing",
                                        "total_wallets": 300,
                                        "processed_wallets": 120,
                                        "percentage": 40,
                                        "stalled": false,
                                        "message": "Analyzing wallets...",
                                        "links": {
                                            "self": "/api/v1/scans/wallet_processing_a1b2c3d4",
                                            "result": "/api/v1/tokens/DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263/top-wallets"
                                        }
                                    },
                                    "request_id": "9f1c2e9a-7b1d-4f6a-9c1e-2a3b4c5d6e7f"
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/ValidationError"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalError"
                    }
                }
            }
        },
        "/api/v1/tokens/{address}/top-wallets": {
            "get": {
                "operationId": "listTokenTopWallets",
                "summary": "Get the top wallets of a scanned token (~300)",
                "description": "Returns the token's metadata and its top wallets — the merged result of the top-traders snapshot and the per-wallet token PnL table, up to 300 entries, each with full wallet stats and 1d/7d/30d history. Requires a prior scan; 404 includes the exact trigger call otherwise. Counts toward the daily read quota.",
                "parameters": [
                    {
                        "$ref": "#/components/parameters/TokenAddress"
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 300,
                            "default": 300
                        },
                        "description": "Number of wallets to return. Strict: out-of-range values are a 400, never silently clamped."
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Token + top wallets",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": ["data", "meta", "request_id"],
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/TokenTopWalletsData"
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/TokenTopWalletsMeta"
                                        },
                                        "request_id": {
                                            "type": "string"
                                        }
                                    }
                                },
                                "example": {
                                    "data": {
                                        "token": {
                                            "address": "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263",
                                            "name": "Bonk",
                                            "symbol": "BONK",
                                            "decimals": 5,
                                            "image": "https://example.com/bonk.png"
                                        },
                                        "top_wallets": [
                                            {
                                                "token_address": "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263",
                                                "wallet_address": "4Nd1mYbJ6PXvDra3DnzgUtQVtUUSDtdQZFLz4WqQbfvF",
                                                "realized": 12500.5,
                                                "first_buy_time": 1733000000000,
                                                "total_invested": 4000,
                                                "wallet": {
                                                    "wallet": {
                                                        "address": "4Nd1mYbJ6PXvDra3DnzgUtQVtUUSDtdQZFLz4WqQbfvF",
                                                        "combined_pnl_usd": 15000,
                                                        "winrate": 62
                                                    },
                                                    "historic": {
                                                        "1d": {},
                                                        "7d": {},
                                                        "30d": {}
                                                    }
                                                }
                                            }
                                        ]
                                    },
                                    "meta": {
                                        "count": 300,
                                        "limit": 300,
                                        "scanned_at": "2026-06-10T12:00:00.000Z",
                                        "views": 7
                                    },
                                    "request_id": "9f1c2e9a-7b1d-4f6a-9c1e-2a3b4c5d6e7f"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/XRequestId"
                            },
                            "X-RateLimit-Limit": {
                                "$ref": "#/components/headers/XRateLimitLimit"
                            },
                            "X-RateLimit-Remaining": {
                                "$ref": "#/components/headers/XRateLimitRemaining"
                            },
                            "X-RateLimit-Reset": {
                                "$ref": "#/components/headers/XRateLimitReset"
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/ValidationError"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalError"
                    }
                }
            }
        },
        "/api/v1/wallets/{address}/scan": {
            "post": {
                "operationId": "scanWallet",
                "summary": "Scan a wallet (synchronous)",
                "description": "Fetches the wallet's PnL from the upstream provider, computes tags and scores, persists everything and returns the result in the same request (< 60 s).\n\nFreshness: a wallet updated less than 1 hour ago is served from the database (`source: \"cache\"`) unless `force_refresh: true`. If the upstream provider fails but older data exists, that data is returned with `source: \"stale-fallback\"`. Counts toward the daily scan quota.",
                "parameters": [
                    {
                        "$ref": "#/components/parameters/WalletAddress"
                    }
                ],
                "requestBody": {
                    "required": false,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/WalletScanRequestBody"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Wallet scan result",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": ["data", "request_id"],
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/WalletScanData"
                                        },
                                        "request_id": {
                                            "type": "string"
                                        }
                                    }
                                },
                                "example": {
                                    "data": {
                                        "address": "4Nd1mYbJ6PXvDra3DnzgUtQVtUUSDtdQZFLz4WqQbfvF",
                                        "source": "fresh",
                                        "scanned_at": "2026-06-10T12:00:00.000Z",
                                        "wallet": {
                                            "address": "4Nd1mYbJ6PXvDra3DnzgUtQVtUUSDtdQZFLz4WqQbfvF",
                                            "score": 72,
                                            "tags": ["early"],
                                            "recentTokens": []
                                        }
                                    },
                                    "request_id": "9f1c2e9a-7b1d-4f6a-9c1e-2a3b4c5d6e7f"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/XRequestId"
                            },
                            "X-RateLimit-Limit": {
                                "$ref": "#/components/headers/XRateLimitLimit"
                            },
                            "X-RateLimit-Remaining": {
                                "$ref": "#/components/headers/XRateLimitRemaining"
                            },
                            "X-RateLimit-Reset": {
                                "$ref": "#/components/headers/XRateLimitReset"
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/ValidationError"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalError"
                    },
                    "502": {
                        "$ref": "#/components/responses/UpstreamError"
                    },
                    "504": {
                        "$ref": "#/components/responses/UpstreamTimeout"
                    }
                }
            }
        },
        "/api/v1/wallets/{address}": {
            "get": {
                "operationId": "getWallet",
                "summary": "Get a wallet from the index (cached read)",
                "description": "Returns the wallet straight from the database — never calls the upstream provider, so it is fast and cheap. `scanned_at` tells you how fresh the data is; use `scanWallet` to refresh. Counts toward the daily read quota.",
                "parameters": [
                    {
                        "$ref": "#/components/parameters/WalletAddress"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Wallet data from the index",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": ["data", "request_id"],
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/WalletScanData"
                                        },
                                        "request_id": {
                                            "type": "string"
                                        }
                                    }
                                },
                                "example": {
                                    "data": {
                                        "address": "4Nd1mYbJ6PXvDra3DnzgUtQVtUUSDtdQZFLz4WqQbfvF",
                                        "source": "cache",
                                        "scanned_at": "2026-06-10T11:20:00.000Z",
                                        "wallet": {
                                            "address": "4Nd1mYbJ6PXvDra3DnzgUtQVtUUSDtdQZFLz4WqQbfvF",
                                            "score": 72,
                                            "tags": ["early"],
                                            "recentTokens": []
                                        }
                                    },
                                    "request_id": "9f1c2e9a-7b1d-4f6a-9c1e-2a3b4c5d6e7f"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/XRequestId"
                            },
                            "X-RateLimit-Limit": {
                                "$ref": "#/components/headers/XRateLimitLimit"
                            },
                            "X-RateLimit-Remaining": {
                                "$ref": "#/components/headers/XRateLimitRemaining"
                            },
                            "X-RateLimit-Reset": {
                                "$ref": "#/components/headers/XRateLimitReset"
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/ValidationError"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "404": {
                        "$ref": "#/components/responses/NotFound"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalError"
                    }
                }
            }
        },
        "/api/v1/correlations": {
            "get": {
                "operationId": "listCorrelations",
                "summary": "Find wallets correlated across 2-5 tokens",
                "description": "Returns wallets that traded at least 2 of the given tokens. This is set-overlap counting, not statistical correlation: `correlation.global` lists the requested mints the wallet traded; `winners` and `first_buyers` restrict each token's list to its top-100 by realized PnL / earliest first buy.\n\nTokens must have been scanned before they contribute data — `meta.tokens_missing` lists the ones to trigger via `createTokenScan`. Results are ordered by global overlap count (DESC) and paginated with `limit`/`offset`; `meta.truncated: true` means more rows may exist.",
                "parameters": [
                    {
                        "name": "tokens",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Comma-separated token mint addresses (2-5 after deduplication)",
                        "example": "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263,EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 500,
                            "default": 100
                        }
                    },
                    {
                        "name": "offset",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 100000,
                            "default": 0
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Correlated wallets",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": ["data", "meta", "request_id"],
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/CorrelationWalletData"
                                            }
                                        },
                                        "meta": {
                                            "$ref": "#/components/schemas/CorrelationMeta"
                                        },
                                        "request_id": {
                                            "type": "string"
                                        }
                                    }
                                },
                                "example": {
                                    "data": [
                                        {
                                            "address": "4Nd1mYbJ6PXvDra3DnzgUtQVtUUSDtdQZFLz4WqQbfvF",
                                            "correlation": {
                                                "global": [
                                                    "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263",
                                                    "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
                                                ],
                                                "winners": [
                                                    "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263"
                                                ],
                                                "first_buyers": [],
                                                "counts": {
                                                    "global": 2,
                                                    "winners": 1,
                                                    "first_buyers": 0,
                                                    "max": 2
                                                }
                                            },
                                            "aggregates": {
                                                "avg_realized_pnl": 8200.4,
                                                "avg_total_invested": 1500,
                                                "avg_first_buy_delay_sec": 95,
                                                "pair_pnl_by_token": [],
                                                "pair_buy_size_by_token": []
                                            },
                                            "matched_tokens": [
                                                {
                                                    "address": "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263",
                                                    "name": "Bonk",
                                                    "symbol": "BONK",
                                                    "image": null
                                                }
                                            ],
                                            "stats": {
                                                "all": {},
                                                "30d": {},
                                                "7d": {},
                                                "1d": {}
                                            }
                                        }
                                    ],
                                    "meta": {
                                        "tokens_requested": 2,
                                        "tokens_found": 2,
                                        "tokens_missing": [],
                                        "tokens_found_details": [],
                                        "wallets_returned": 1,
                                        "limit": 100,
                                        "offset": 0,
                                        "truncated": false
                                    },
                                    "request_id": "9f1c2e9a-7b1d-4f6a-9c1e-2a3b4c5d6e7f"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/XRequestId"
                            },
                            "X-RateLimit-Limit": {
                                "$ref": "#/components/headers/XRateLimitLimit"
                            },
                            "X-RateLimit-Remaining": {
                                "$ref": "#/components/headers/XRateLimitRemaining"
                            },
                            "X-RateLimit-Reset": {
                                "$ref": "#/components/headers/XRateLimitReset"
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/ValidationError"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthorized"
                    },
                    "403": {
                        "$ref": "#/components/responses/Forbidden"
                    },
                    "429": {
                        "$ref": "#/components/responses/RateLimited"
                    },
                    "500": {
                        "$ref": "#/components/responses/InternalError"
                    },
                    "504": {
                        "$ref": "#/components/responses/UpstreamTimeout"
                    }
                }
            }
        }
    },
    "components": {
        "securitySchemes": {
            "bearerAuth": {
                "type": "http",
                "scheme": "bearer",
                "description": "API key issued by the TopWallets team (format: tw_live_...). Requires an active Pro or Premium subscription. Keys must never be shipped to browsers."
            }
        },
        "parameters": {
            "TokenAddress": {
                "name": "address",
                "in": "path",
                "required": true,
                "schema": {
                    "type": "string",
                    "pattern": "^[1-9A-HJ-NP-Za-km-z]{32,44}$"
                },
                "description": "Token mint address (base58)"
            },
            "WalletAddress": {
                "name": "address",
                "in": "path",
                "required": true,
                "schema": {
                    "type": "string",
                    "pattern": "^[1-9A-HJ-NP-Za-km-z]{32,44}$"
                },
                "description": "Wallet address (base58)"
            }
        },
        "schemas": {
            "ApiV1Failure": {
                "type": "object",
                "required": ["error", "request_id", "documentation_url"],
                "properties": {
                    "error": {
                        "type": "object",
                        "required": ["code", "message"],
                        "properties": {
                            "code": {
                                "type": "string",
                                "enum": [
                                    "UNAUTHORIZED",
                                    "FORBIDDEN_PLAN",
                                    "QUOTA_EXCEEDED",
                                    "VALIDATION_ERROR",
                                    "TOKEN_NOT_SCANNED",
                                    "SCAN_NOT_FOUND",
                                    "WALLET_NOT_FOUND",
                                    "RATE_LIMITED",
                                    "UPSTREAM_ERROR",
                                    "UPSTREAM_TIMEOUT",
                                    "INTERNAL_ERROR"
                                ]
                            },
                            "message": {
                                "type": "string"
                            },
                            "details": {
                                "description": "Either a list of field errors ({ field, message, received }) or a context object — e.g. { used, limit, resets_at } on QUOTA_EXCEEDED, or { reason: \"quota_verification_unavailable\", resets_at } when the quota could not be verified (retry immediately)"
                            }
                        }
                    },
                    "request_id": {
                        "type": "string"
                    },
                    "documentation_url": {
                        "type": "string"
                    }
                }
            },
            "TokenScanTriggerData": {
                "type": "object",
                "required": [
                    "scan_id",
                    "token_address",
                    "status",
                    "reused",
                    "is_update",
                    "links"
                ],
                "properties": {
                    "scan_id": {
                        "type": "string",
                        "description": "Opaque scan identifier; pass to getTokenScan"
                    },
                    "token_address": {
                        "type": "string"
                    },
                    "status": {
                        "type": "string",
                        "const": "processing"
                    },
                    "reused": {
                        "type": "boolean",
                        "description": "true = an active scan already existed; nothing was enqueued and no quota was consumed"
                    },
                    "is_update": {
                        "type": "boolean",
                        "description": "true = the token already had a report before this trigger (always false when reused)"
                    },
                    "links": {
                        "type": "object",
                        "required": ["status", "result"],
                        "properties": {
                            "status": {
                                "type": "string"
                            },
                            "result": {
                                "type": "string"
                            }
                        }
                    }
                }
            },
            "ScanStatusData": {
                "type": "object",
                "required": [
                    "scan_id",
                    "token_address",
                    "status",
                    "total_wallets",
                    "processed_wallets",
                    "percentage",
                    "stalled",
                    "message",
                    "links"
                ],
                "properties": {
                    "scan_id": {
                        "type": "string"
                    },
                    "token_address": {
                        "type": "string"
                    },
                    "status": {
                        "type": "string",
                        "enum": ["processing", "completed", "failed"]
                    },
                    "total_wallets": {
                        "type": "integer"
                    },
                    "processed_wallets": {
                        "type": "integer"
                    },
                    "percentage": {
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 100
                    },
                    "stalled": {
                        "type": "boolean",
                        "description": "processing with no progress update for 20+ minutes — safe to re-trigger"
                    },
                    "message": {
                        "type": ["string", "null"]
                    },
                    "links": {
                        "type": "object",
                        "required": ["self", "result"],
                        "properties": {
                            "self": {
                                "type": "string"
                            },
                            "result": {
                                "type": "string"
                            }
                        }
                    }
                }
            },
            "Token": {
                "type": "object",
                "description": "Token row as stored by the scan pipeline (metadata, pools, risk, bundlers, ...)",
                "required": ["address", "name", "symbol"],
                "properties": {
                    "address": {
                        "type": "string"
                    },
                    "name": {
                        "type": "string"
                    },
                    "symbol": {
                        "type": "string"
                    },
                    "decimals": {
                        "type": "integer"
                    },
                    "image": {
                        "type": "string"
                    },
                    "description": {
                        "type": ["string", "null"]
                    },
                    "extensions": {
                        "type": "object"
                    },
                    "pools": {
                        "type": "array"
                    },
                    "risk": {
                        "type": "object"
                    },
                    "token_creation_time": {
                        "type": ["integer", "null"]
                    },
                    "updated_at": {
                        "type": "string"
                    }
                },
                "additionalProperties": true
            },
            "TokenTopWallet": {
                "type": "object",
                "description": "One top wallet of a token: its per-token trade stats plus the full wallet profile with 1d/7d/30d history",
                "required": ["token_address", "wallet_address", "wallet"],
                "properties": {
                    "token_address": {
                        "type": "string"
                    },
                    "wallet_address": {
                        "type": "string"
                    },
                    "realized": {
                        "type": "number",
                        "description": "Realized PnL (USD) on this token"
                    },
                    "first_buy_time": {
                        "type": ["number", "null"],
                        "description": "Epoch ms of the wallet's first buy of this token"
                    },
                    "total_invested": {
                        "type": "number"
                    },
                    "wallet": {
                        "type": "object",
                        "description": "WalletWithHistoric: { wallet: {...}, historic: { '1d': {...}, '7d': {...}, '30d': {...} } } — full PnL stats, score (0-99) and tags per period",
                        "additionalProperties": true
                    }
                },
                "additionalProperties": true
            },
            "TokenTopWalletsData": {
                "type": "object",
                "required": ["token", "top_wallets"],
                "properties": {
                    "token": {
                        "$ref": "#/components/schemas/Token"
                    },
                    "top_wallets": {
                        "type": "array",
                        "maxItems": 300,
                        "items": {
                            "$ref": "#/components/schemas/TokenTopWallet"
                        }
                    }
                }
            },
            "TokenTopWalletsMeta": {
                "type": "object",
                "required": ["count", "limit", "scanned_at", "views"],
                "properties": {
                    "count": {
                        "type": "integer"
                    },
                    "limit": {
                        "type": "integer"
                    },
                    "scanned_at": {
                        "type": "string",
                        "description": "When the token data was last refreshed (ISO 8601)"
                    },
                    "views": {
                        "type": "integer"
                    }
                }
            },
            "WalletScanRequestBody": {
                "type": "object",
                "properties": {
                    "force_refresh": {
                        "type": "boolean",
                        "default": false,
                        "description": "Bypass the 1-hour freshness window and re-fetch from the upstream provider"
                    }
                },
                "additionalProperties": true
            },
            "WalletScanData": {
                "type": "object",
                "required": ["address", "source", "scanned_at", "wallet"],
                "properties": {
                    "address": {
                        "type": "string"
                    },
                    "source": {
                        "type": "string",
                        "enum": ["fresh", "cache", "stale-fallback"],
                        "description": "fresh = fetched from the upstream provider now; cache = database, less than 1 h old; stale-fallback = upstream failed, older database data returned. getWallet always returns cache."
                    },
                    "scanned_at": {
                        "type": "string",
                        "description": "When the returned data was produced (ISO 8601)"
                    },
                    "wallet": {
                        "type": "object",
                        "description": "30d trading stats (PnL, winrate, ROI, score 0-99, tags) plus up to 3 recent tokens",
                        "additionalProperties": true
                    }
                }
            },
            "CorrelationWalletData": {
                "type": "object",
                "required": [
                    "address",
                    "correlation",
                    "aggregates",
                    "matched_tokens",
                    "stats"
                ],
                "properties": {
                    "address": {
                        "type": "string"
                    },
                    "correlation": {
                        "type": "object",
                        "required": [
                            "global",
                            "winners",
                            "first_buyers",
                            "counts"
                        ],
                        "properties": {
                            "global": {
                                "type": "array",
                                "items": {
                                    "type": "string"
                                },
                                "description": "Requested mints this wallet traded"
                            },
                            "winners": {
                                "type": "array",
                                "items": {
                                    "type": "string"
                                },
                                "description": "Mints where this wallet is in the per-token top 100 by realized PnL"
                            },
                            "first_buyers": {
                                "type": "array",
                                "items": {
                                    "type": "string"
                                },
                                "description": "Mints where this wallet is in the per-token first 100 buyers"
                            },
                            "counts": {
                                "type": "object",
                                "required": [
                                    "global",
                                    "winners",
                                    "first_buyers",
                                    "max"
                                ],
                                "properties": {
                                    "global": {
                                        "type": "integer"
                                    },
                                    "winners": {
                                        "type": "integer"
                                    },
                                    "first_buyers": {
                                        "type": "integer"
                                    },
                                    "max": {
                                        "type": "integer"
                                    }
                                }
                            }
                        }
                    },
                    "aggregates": {
                        "type": "object",
                        "required": [
                            "avg_realized_pnl",
                            "avg_total_invested",
                            "avg_first_buy_delay_sec",
                            "pair_pnl_by_token",
                            "pair_buy_size_by_token"
                        ],
                        "properties": {
                            "avg_realized_pnl": {
                                "type": "number",
                                "description": "Average realized PnL across the matched tokens (USD)"
                            },
                            "avg_total_invested": {
                                "type": "number"
                            },
                            "avg_first_buy_delay_sec": {
                                "type": ["number", "null"],
                                "description": "Average delay between token creation and this wallet's first buy (seconds)"
                            },
                            "pair_pnl_by_token": {
                                "type": "array",
                                "items": {
                                    "type": "object",
                                    "properties": {
                                        "address": {
                                            "type": "string"
                                        },
                                        "name": {
                                            "type": "string"
                                        },
                                        "symbol": {
                                            "type": "string"
                                        },
                                        "pnl": {
                                            "type": "number"
                                        }
                                    }
                                }
                            },
                            "pair_buy_size_by_token": {
                                "type": "array",
                                "items": {
                                    "type": "object",
                                    "properties": {
                                        "address": {
                                            "type": "string"
                                        },
                                        "name": {
                                            "type": "string"
                                        },
                                        "symbol": {
                                            "type": "string"
                                        },
                                        "buySize": {
                                            "type": "number"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "matched_tokens": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "required": ["address", "name", "symbol"],
                            "properties": {
                                "address": {
                                    "type": "string"
                                },
                                "name": {
                                    "type": "string"
                                },
                                "symbol": {
                                    "type": "string"
                                },
                                "image": {
                                    "type": ["string", "null"]
                                }
                            }
                        }
                    },
                    "stats": {
                        "type": "object",
                        "description": "Per-period trading stats keyed all/30d/7d/1d (score, PnL, winrate, tags, ...). Correlation fields are not duplicated here.",
                        "additionalProperties": true
                    }
                }
            },
            "CorrelationMeta": {
                "type": "object",
                "required": [
                    "tokens_requested",
                    "tokens_found",
                    "tokens_missing",
                    "tokens_found_details",
                    "wallets_returned",
                    "limit",
                    "offset",
                    "truncated"
                ],
                "properties": {
                    "tokens_requested": {
                        "type": "integer"
                    },
                    "tokens_found": {
                        "type": "integer"
                    },
                    "tokens_missing": {
                        "type": "array",
                        "items": {
                            "type": "string"
                        },
                        "description": "Mints with no scanned data yet — trigger createTokenScan for each"
                    },
                    "tokens_found_details": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "address": {
                                    "type": "string"
                                },
                                "name": {
                                    "type": "string"
                                },
                                "symbol": {
                                    "type": "string"
                                },
                                "image": {
                                    "type": ["string", "null"]
                                }
                            }
                        }
                    },
                    "wallets_returned": {
                        "type": "integer"
                    },
                    "limit": {
                        "type": "integer"
                    },
                    "offset": {
                        "type": "integer"
                    },
                    "truncated": {
                        "type": "boolean",
                        "description": "wallets_returned === limit ⇒ more rows may exist"
                    }
                }
            }
        },
        "responses": {
            "ValidationError": {
                "description": "Invalid request parameters",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/ApiV1Failure"
                        },
                        "example": {
                            "error": {
                                "code": "VALIDATION_ERROR",
                                "message": "Invalid request parameters.",
                                "details": [
                                    {
                                        "field": "tokens[1]",
                                        "message": "Not a valid Solana address",
                                        "received": "abc"
                                    }
                                ]
                            },
                            "request_id": "9f1c2e9a-7b1d-4f6a-9c1e-2a3b4c5d6e7f",
                            "documentation_url": "https://www.topwallets.ai/docs/api#errors-validation"
                        }
                    }
                },
                "headers": {
                    "X-Request-Id": {
                        "$ref": "#/components/headers/XRequestId"
                    }
                }
            },
            "Unauthorized": {
                "description": "Missing or invalid API key",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/ApiV1Failure"
                        },
                        "example": {
                            "error": {
                                "code": "UNAUTHORIZED",
                                "message": "Invalid API key"
                            },
                            "request_id": "9f1c2e9a-7b1d-4f6a-9c1e-2a3b4c5d6e7f",
                            "documentation_url": "https://www.topwallets.ai/docs/api#authentication"
                        }
                    }
                },
                "headers": {
                    "X-Request-Id": {
                        "$ref": "#/components/headers/XRequestId"
                    }
                }
            },
            "Forbidden": {
                "description": "Plan does not allow API access (FORBIDDEN_PLAN). On quota-enforced operations (createTokenScan, scanWallet, listTokenTopWallets, getWallet) the daily quota may also be exhausted (QUOTA_EXCEEDED).",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/ApiV1Failure"
                        },
                        "example": {
                            "error": {
                                "code": "QUOTA_EXCEEDED",
                                "message": "Daily scan quota reached for the pro plan",
                                "details": {
                                    "used": 10,
                                    "limit": 10,
                                    "resets_at": "2026-06-11T00:00:00.000Z"
                                }
                            },
                            "request_id": "9f1c2e9a-7b1d-4f6a-9c1e-2a3b4c5d6e7f",
                            "documentation_url": "https://www.topwallets.ai/docs/api#rate-limits-daily-quotas"
                        }
                    }
                },
                "headers": {
                    "X-Request-Id": {
                        "$ref": "#/components/headers/XRequestId"
                    }
                }
            },
            "NotFound": {
                "description": "Resource not found — the message and details name the exact call that creates it",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/ApiV1Failure"
                        },
                        "example": {
                            "error": {
                                "code": "TOKEN_NOT_SCANNED",
                                "message": "No report exists for this token yet. Trigger one with POST /api/v1/tokens/{address}/scans.",
                                "details": {
                                    "trigger": "POST /api/v1/tokens/DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263/scans"
                                }
                            },
                            "request_id": "9f1c2e9a-7b1d-4f6a-9c1e-2a3b4c5d6e7f",
                            "documentation_url": "https://www.topwallets.ai/docs/api#token-scans-result"
                        }
                    }
                },
                "headers": {
                    "X-Request-Id": {
                        "$ref": "#/components/headers/XRequestId"
                    }
                }
            },
            "RateLimited": {
                "description": "Per-minute rate limit exceeded; retry after Retry-After seconds",
                "headers": {
                    "Retry-After": {
                        "schema": {
                            "type": "string"
                        }
                    },
                    "X-Request-Id": {
                        "$ref": "#/components/headers/XRequestId"
                    }
                },
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/ApiV1Failure"
                        },
                        "example": {
                            "error": {
                                "code": "RATE_LIMITED",
                                "message": "Rate limit of 60 requests per minute exceeded",
                                "details": {
                                    "limit": 60,
                                    "retry_after": 21
                                }
                            },
                            "request_id": "9f1c2e9a-7b1d-4f6a-9c1e-2a3b4c5d6e7f",
                            "documentation_url": "https://www.topwallets.ai/docs/api#rate-limits"
                        }
                    }
                }
            },
            "InternalError": {
                "description": "Unexpected server error — report the request_id",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/ApiV1Failure"
                        },
                        "example": {
                            "error": {
                                "code": "INTERNAL_ERROR",
                                "message": "Internal server error"
                            },
                            "request_id": "9f1c2e9a-7b1d-4f6a-9c1e-2a3b4c5d6e7f",
                            "documentation_url": "https://www.topwallets.ai/docs/api#errors"
                        }
                    }
                },
                "headers": {
                    "X-Request-Id": {
                        "$ref": "#/components/headers/XRequestId"
                    }
                }
            },
            "UpstreamError": {
                "description": "The upstream data provider failed",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/ApiV1Failure"
                        },
                        "example": {
                            "error": {
                                "code": "UPSTREAM_ERROR",
                                "message": "Failed to fetch wallet data from the upstream provider. Please retry."
                            },
                            "request_id": "9f1c2e9a-7b1d-4f6a-9c1e-2a3b4c5d6e7f",
                            "documentation_url": "https://www.topwallets.ai/docs/api#errors-upstream"
                        }
                    }
                },
                "headers": {
                    "X-Request-Id": {
                        "$ref": "#/components/headers/XRequestId"
                    }
                }
            },
            "UpstreamTimeout": {
                "description": "The upstream data provider or query timed out",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/ApiV1Failure"
                        },
                        "example": {
                            "error": {
                                "code": "UPSTREAM_TIMEOUT",
                                "message": "The correlation query timed out. Retry with fewer tokens or a smaller limit."
                            },
                            "request_id": "9f1c2e9a-7b1d-4f6a-9c1e-2a3b4c5d6e7f",
                            "documentation_url": "https://www.topwallets.ai/docs/api#errors-upstream"
                        }
                    }
                },
                "headers": {
                    "X-Request-Id": {
                        "$ref": "#/components/headers/XRequestId"
                    }
                }
            }
        },
        "headers": {
            "XRequestId": {
                "schema": {
                    "type": "string"
                },
                "description": "Unique request id, echoed in the body's request_id — quote it in support requests"
            },
            "XRateLimitLimit": {
                "schema": {
                    "type": "string"
                },
                "description": "Per-minute limit for this key (absent for fail-open and internal calls)"
            },
            "XRateLimitRemaining": {
                "schema": {
                    "type": "string"
                },
                "description": "Requests remaining in the current window"
            },
            "XRateLimitReset": {
                "schema": {
                    "type": "string"
                },
                "description": "Epoch seconds when the window resets"
            }
        }
    }
}
