{
  "openapi": "3.1.0",
  "info": {
    "title": "Polyshadow API",
    "version": "1.0.0",
    "description": "Programmatic access to Polyshadow trader tracking data. Requires a Pro or Max subscription and an API token generated from your dashboard."
  },
  "servers": [
    { "url": "https://api.polyshadow.net", "description": "Production" }
  ],
  "components": {
    "securitySchemes": {
      "ApiToken": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "shadow_token",
        "description": "API token generated from /dashboard/api. Prefix: shadow_"
      }
    },
    "headers": {
      "X-RateLimit-Limit": {
        "description": "Maximum requests allowed per 60-second window",
        "schema": { "type": "integer" }
      },
      "X-RateLimit-Remaining": {
        "description": "Requests remaining in the current window",
        "schema": { "type": "integer" }
      },
      "X-RateLimit-Reset": {
        "description": "Unix timestamp when the current window resets",
        "schema": { "type": "integer" }
      },
      "Retry-After": {
        "description": "Seconds to wait before retrying (only present on 429 responses)",
        "schema": { "type": "integer" }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Invalid or missing API token",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "RateLimited": {
        "description": "Rate limit exceeded (100 requests per 60 seconds per token)",
        "headers": {
          "Retry-After": { "$ref": "#/components/headers/Retry-After" }
        },
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "error":      { "type": "string" },
                "retryAfter": { "type": "integer", "description": "Seconds until the rate limit window resets" }
              },
              "required": ["error", "retryAfter"]
            }
          }
        }
      }
    },
    "schemas": {
      "TrackedWallet": {
        "type": "object",
        "properties": {
          "id":           { "type": "string", "format": "uuid" },
          "userId":       { "type": "string" },
          "address":      { "type": "string", "example": "0xcb15489ebdbe6c0e48e2b2efb0f5ab23d62401c6" },
          "label":        { "type": ["string", "null"] },
          "username":     { "type": ["string", "null"] },
          "profileImage": { "type": ["string", "null"] },
          "createdAt":    { "type": "string", "format": "date-time" }
        },
        "required": ["id", "userId", "address", "createdAt"]
      },
      "TradeEvent": {
        "type": "object",
        "properties": {
          "id":               { "type": "string" },
          "walletAddress":    { "type": "string" },
          "walletUsername":   { "type": ["string", "null"], "description": "Polymarket username for the wallet, if known" },
          "marketId":         { "type": "string" },
          "marketSlug":       { "type": "string" },
          "marketName":       { "type": "string" },
          "marketCategory":   { "type": ["string", "null"] },
          "marketIcon":       { "type": ["string", "null"], "description": "URL of the market icon image" },
          "outcome":          { "type": "string", "example": "Yes" },
          "side":             { "type": "string", "enum": ["BUY", "SELL"] },
          "size":             { "type": "number", "description": "Number of shares" },
          "price":            { "type": "number", "description": "Price per share (0–1, implied probability)" },
          "usdcValue":        { "type": "number", "description": "USD value of the trade" },
          "timestamp":        { "type": "string", "format": "date-time" },
          "transactionHash":  { "type": ["string", "null"] }
        },
        "required": ["id", "walletAddress", "marketId", "marketSlug", "marketName", "outcome", "side", "size", "price", "usdcValue", "timestamp"]
      },
      "MarketStatus": {
        "type": "object",
        "properties": {
          "marketId":       { "type": "string" },
          "closed":         { "type": "boolean" },
          "winningOutcome": { "type": ["string", "null"] }
        },
        "required": ["marketId", "closed", "winningOutcome"]
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": { "type": "string" }
        },
        "required": ["error"]
      }
    }
  },
  "security": [{ "ApiToken": [] }],
  "paths": {
    "/traders": {
      "get": {
        "summary": "List tracked wallets",
        "operationId": "listTraders",
        "tags": ["Wallets"],
        "responses": {
          "200": {
            "description": "Array of tracked wallets for the authenticated user",
            "headers": {
              "X-RateLimit-Limit":     { "$ref": "#/components/headers/X-RateLimit-Limit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/X-RateLimit-Remaining" },
              "X-RateLimit-Reset":     { "$ref": "#/components/headers/X-RateLimit-Reset" }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "wallets": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/TrackedWallet" }
                    }
                  },
                  "required": ["wallets"]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "post": {
        "summary": "Add a wallet to track",
        "operationId": "addTrader",
        "tags": ["Wallets"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "address": { "type": "string", "description": "Wallet address (0x…) or Polymarket username" },
                  "label":   { "type": "string", "description": "Optional human-readable label" }
                },
                "required": ["address"]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Wallet added and tracking started",
            "headers": {
              "X-RateLimit-Limit":     { "$ref": "#/components/headers/X-RateLimit-Limit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/X-RateLimit-Remaining" },
              "X-RateLimit-Reset":     { "$ref": "#/components/headers/X-RateLimit-Reset" }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "wallet": { "$ref": "#/components/schemas/TrackedWallet" }
                  },
                  "required": ["wallet"]
                }
              }
            }
          },
          "400": { "description": "Invalid address format", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": {
            "description": "Wallet limit reached for your plan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error":      { "type": "string" },
                    "tier":       { "type": "string", "enum": ["free", "pro", "max"] },
                    "maxWallets": { "type": "integer" }
                  },
                  "required": ["error", "tier", "maxWallets"]
                }
              }
            }
          },
          "409": { "description": "Wallet already tracked", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/traders/{address}": {
      "delete": {
        "summary": "Remove a tracked wallet",
        "operationId": "removeTrader",
        "tags": ["Wallets"],
        "parameters": [
          {
            "name": "address",
            "in": "path",
            "required": true,
            "schema": { "type": "string" },
            "description": "Wallet address to stop tracking"
          }
        ],
        "responses": {
          "200": {
            "description": "Wallet removed",
            "headers": {
              "X-RateLimit-Limit":     { "$ref": "#/components/headers/X-RateLimit-Limit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/X-RateLimit-Remaining" },
              "X-RateLimit-Reset":     { "$ref": "#/components/headers/X-RateLimit-Reset" }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "success": { "type": "boolean" } },
                  "required": ["success"]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "description": "Wallet not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/trades": {
      "get": {
        "summary": "Get trade history",
        "operationId": "getTrades",
        "tags": ["Trades"],
        "description": "Returns paginated trade history for a tracked wallet, grouped by market. Only wallets owned by the authenticated user may be queried.",
        "parameters": [
          {
            "name": "address",
            "in": "query",
            "required": true,
            "schema": { "type": "string" },
            "description": "Wallet address to query"
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "schema": { "type": "string", "format": "date-time" },
            "description": "Start of date range (ISO 8601). Defaults to epoch."
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "schema": { "type": "string", "format": "date-time" },
            "description": "End of date range (ISO 8601). Defaults to now."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 20 },
            "description": "Number of markets per page (max 100)"
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": { "type": "string" },
            "description": "Pagination cursor returned by a previous response"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated trade events plus current market statuses",
            "headers": {
              "X-RateLimit-Limit":     { "$ref": "#/components/headers/X-RateLimit-Limit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/X-RateLimit-Remaining" },
              "X-RateLimit-Reset":     { "$ref": "#/components/headers/X-RateLimit-Reset" }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "trades": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/TradeEvent" }
                    },
                    "marketStatuses": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/MarketStatus" }
                    },
                    "cursor":  { "type": ["string", "null"], "description": "Pass as cursor on the next request to get the next page" },
                    "hasMore": { "type": "boolean" }
                  },
                  "required": ["trades", "marketStatuses", "cursor", "hasMore"]
                }
              }
            }
          },
          "400": { "description": "Missing address param", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "description": "Wallet not tracked by this account", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "description": "Internal server error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/me/subscription": {
      "get": {
        "summary": "Get subscription details",
        "operationId": "getSubscription",
        "tags": ["Account"],
        "responses": {
          "200": {
            "description": "Current subscription tier and limits",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "subscription": {
                      "type": "object",
                      "properties": {
                        "tier":             { "type": "string", "enum": ["free", "pro", "max"] },
                        "status":           { "type": "string", "enum": ["active", "past_due", "canceled", "trialing"] },
                        "maxWallets":       { "type": "integer" },
                        "historyAccess":    { "type": "boolean" },
                        "currentPeriodEnd": { "type": ["string", "null"], "format": "date-time" }
                      },
                      "required": ["tier", "status", "maxWallets", "historyAccess"]
                    }
                  },
                  "required": ["subscription"]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/me/api-token": {
      "get": {
        "summary": "Get API token metadata",
        "operationId": "getApiToken",
        "tags": ["Account"],
        "description": "Returns metadata about the current API token. The token value itself is never returned after creation.",
        "responses": {
          "200": {
            "description": "Token metadata, or null if no token exists",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "token": {
                      "oneOf": [
                        {
                          "type": "object",
                          "properties": {
                            "id":          { "type": "string", "format": "uuid" },
                            "createdAt":   { "type": "string", "format": "date-time" },
                            "lastUsedAt":  { "type": ["string", "null"], "format": "date-time" }
                          },
                          "required": ["id", "createdAt"]
                        },
                        { "type": "null" }
                      ]
                    }
                  },
                  "required": ["token"]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      },
      "post": {
        "summary": "Generate or rotate API token",
        "operationId": "createApiToken",
        "tags": ["Account"],
        "description": "Creates a new API token, revoking any existing one. The plaintext token is returned exactly once and cannot be retrieved again.",
        "responses": {
          "201": {
            "description": "New token. Store this value immediately — it will not be shown again.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "token": { "type": "string", "example": "shadow_a3f2...e91b" }
                  },
                  "required": ["token"]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "description": "Pro or Max subscription required", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      },
      "delete": {
        "summary": "Revoke API token",
        "operationId": "deleteApiToken",
        "tags": ["Account"],
        "responses": {
          "204": { "description": "Token revoked" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    }
  }
}
