{
  "openapi": "3.1.0",
  "info": {
    "title": "Bitwyre REST API",
    "version": "0.1.0",
    "summary": "Trade, custody and OTC endpoints for the Bitwyre exchange.",
    "description": "Authenticated endpoints require a `Authorization: Bearer <JWT>` header. JWTs are obtained by completing a challenge flow at `/auth/login` + `/auth/verify`. All amounts are decimal strings to avoid floating-point rounding. All timestamps are ISO 8601 UTC.",
    "contact": {
      "name": "Bitwyre Engineering",
      "url": "https://bitwyre.com"
    }
  },
  "servers": [
    { "url": "https://api.bitwyre.com", "description": "Production" },
    { "url": "https://api-sandbox.bitwyre.com", "description": "Sandbox" },
    { "url": "http://localhost:3001", "description": "Local" }
  ],
  "tags": [
    { "name": "auth", "description": "Registration, login, 2FA, sessions" },
    { "name": "kyc", "description": "Individual identity verification" },
    { "name": "kyb", "description": "Corporate onboarding" },
    { "name": "wallet", "description": "Balances, deposits, withdrawals" },
    { "name": "orders", "description": "Order lifecycle on the matching engine" },
    { "name": "markets", "description": "Public market data" },
    { "name": "rfq", "description": "OTC request-for-quote" },
    { "name": "admin", "description": "KYC/KYB review (requires admin role)" }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": { "type": "http", "scheme": "bearer", "bearerFormat": "JWT" }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": ["error", "message"],
        "properties": {
          "error": { "type": "string", "example": "bad_request" },
          "message": { "type": "string" }
        }
      },
      "RegisterRequest": {
        "type": "object",
        "required": ["email", "password"],
        "properties": {
          "email": { "type": "string", "format": "email" },
          "password": { "type": "string", "minLength": 10 }
        }
      },
      "RegisterResponse": {
        "type": "object",
        "required": ["userId", "verificationSent", "isDryRun"],
        "properties": {
          "userId": { "type": "integer", "format": "int64" },
          "verificationSent": { "type": "boolean" },
          "isDryRun": { "type": "boolean", "description": "True when RESEND_API_KEY is unset and the code was only logged." }
        }
      },
      "VerifyEmailRequest": {
        "type": "object",
        "required": ["email", "code"],
        "properties": {
          "email": { "type": "string", "format": "email" },
          "code": { "type": "string", "minLength": 6, "maxLength": 6 }
        }
      },
      "LoginRequest": {
        "type": "object",
        "required": ["email", "password"],
        "properties": {
          "email": { "type": "string", "format": "email" },
          "password": { "type": "string" }
        }
      },
      "LoginResponse": {
        "type": "object",
        "required": ["challengeId", "requiredMethods"],
        "properties": {
          "challengeId": { "type": "string" },
          "requiredMethods": {
            "type": "array",
            "items": { "type": "string", "enum": ["totp", "passkey", "email"] }
          }
        }
      },
      "VerifyRequest": {
        "type": "object",
        "required": ["challengeId", "method", "code"],
        "properties": {
          "challengeId": { "type": "string" },
          "method": { "type": "string", "enum": ["totp", "passkey", "email"] },
          "code": { "type": "string" }
        }
      },
      "AuthToken": {
        "type": "object",
        "required": ["accessToken", "refreshToken", "expiresIn"],
        "properties": {
          "accessToken": { "type": "string" },
          "refreshToken": { "type": "string" },
          "expiresIn": { "type": "integer" }
        }
      },
      "IndividualProfile": {
        "type": "object",
        "properties": {
          "status": { "type": "string", "enum": ["not_started", "in_progress", "pending_review", "approved", "rejected", "info_requested"] },
          "provider": { "type": "string", "enum": ["own", "sumsub"] },
          "firstName": { "type": "string" },
          "middleName": { "type": "string" },
          "lastName": { "type": "string" },
          "dateOfBirth": { "type": "string", "format": "date" },
          "nationality": { "type": "string", "description": "ISO 3166-1 alpha-2" },
          "residenceCountry": { "type": "string", "description": "ISO 3166-1 alpha-2" },
          "idDocumentKind": { "type": "string", "enum": ["passport", "drivers_license", "national_id"] },
          "idDocumentCountry": { "type": "string" }
        }
      },
      "DocumentRegisterRequest": {
        "type": "object",
        "required": ["kind", "objectKey", "mimeType", "sizeBytes", "sha256Hex"],
        "properties": {
          "kind": { "type": "string", "description": "See /docs for allowed values" },
          "objectKey": { "type": "string" },
          "mimeType": { "type": "string" },
          "sizeBytes": { "type": "integer", "format": "int64" },
          "sha256Hex": { "type": "string", "minLength": 64, "maxLength": 64 }
        }
      },
      "CorporateProfile": {
        "type": "object",
        "properties": {
          "status": { "type": "string" },
          "provider": { "type": "string", "enum": ["own", "sumsub"] },
          "legalName": { "type": "string" },
          "tradingName": { "type": "string" },
          "registrationNumber": { "type": "string" },
          "jurisdiction": { "type": "string" },
          "incorporationDate": { "type": "string", "format": "date" },
          "lineOfBusiness": { "type": "string" },
          "incorporatedOver1y": { "type": "boolean" }
        }
      },
      "Shareholder": {
        "type": "object",
        "required": ["fullName"],
        "properties": {
          "id": { "type": "integer", "format": "int64" },
          "fullName": { "type": "string" },
          "dateOfBirth": { "type": "string", "format": "date" },
          "nationality": { "type": "string" },
          "residenceCountry": { "type": "string" },
          "sharePercent": { "type": "number" },
          "isUbo": { "type": "boolean" },
          "isDirector": { "type": "boolean" }
        }
      },
      "BalanceInfo": {
        "type": "object",
        "properties": {
          "currency": { "type": "string" },
          "available": { "type": "string" },
          "locked": { "type": "string" },
          "total": { "type": "string" }
        }
      },
      "SubmitOrderRequest": {
        "type": "object",
        "required": ["symbol", "side", "orderType", "quantity"],
        "properties": {
          "symbol": { "type": "string", "example": "BTC/USDT" },
          "side": { "type": "string", "enum": ["Buy", "Sell"] },
          "orderType": { "type": "string", "enum": ["Market", "Limit", "Stop", "StopLimit"] },
          "timeInForce": { "type": "string", "enum": ["GTC", "IOC", "FOK", "Day"] },
          "quantity": { "type": "string", "example": "0.25" },
          "price": { "type": "string", "example": "78331.20" },
          "stopPrice": { "type": "string" },
          "clientOrderId": { "type": "string" }
        }
      },
      "Ticker": {
        "type": "object",
        "properties": {
          "symbol": { "type": "string" },
          "last": { "type": "string" },
          "bid": { "type": "string" },
          "ask": { "type": "string" },
          "volume24h": { "type": "string" },
          "change24h": { "type": "string" }
        }
      }
    }
  },
  "paths": {
    "/api/v1/auth/register": {
      "post": {
        "tags": ["auth"],
        "summary": "Create a new user, email a 6-digit verification code",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RegisterRequest" } } } },
        "responses": {
          "200": { "description": "User created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RegisterResponse" } } } },
          "400": { "description": "Email already registered or password too short", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/v1/auth/email/verify": {
      "post": {
        "tags": ["auth"],
        "summary": "Consume a registration verification code",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/VerifyEmailRequest" } } } },
        "responses": {
          "200": { "description": "Email verified" },
          "401": { "description": "Invalid or expired code" }
        }
      }
    },
    "/api/v1/auth/email/request": {
      "post": {
        "tags": ["auth"],
        "summary": "Mid-login: issue a 6-digit email 2FA code against an active challenge",
        "responses": { "200": { "description": "Code emailed" }, "404": { "description": "Challenge not found" } }
      }
    },
    "/api/v1/auth/login": {
      "post": {
        "tags": ["auth"],
        "summary": "Start a login challenge — returns required 2FA methods",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LoginRequest" } } } },
        "responses": {
          "200": { "description": "Challenge started", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LoginResponse" } } } },
          "401": { "description": "Invalid email or password" }
        }
      }
    },
    "/api/v1/auth/verify": {
      "post": {
        "tags": ["auth"],
        "summary": "Complete one 2FA step; returns tokens when all methods verified",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/VerifyRequest" } } } },
        "responses": {
          "200": { "description": "Tokens issued", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AuthToken" } } } },
          "401": { "description": "Invalid code" }
        }
      }
    },
    "/api/v1/kyc/individual": {
      "get": { "tags": ["kyc"], "summary": "Read current KYC profile", "security": [{ "bearerAuth": [] }], "responses": { "200": { "description": "Profile", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/IndividualProfile" } } } } } },
      "post": { "tags": ["kyc"], "summary": "Upsert KYC profile (partial updates allowed via COALESCE)", "security": [{ "bearerAuth": [] }], "responses": { "200": { "description": "Updated profile" } } }
    },
    "/api/v1/kyc/individual/submit": {
      "post": { "tags": ["kyc"], "summary": "Submit for review — gates on required fields + at least one ID document", "security": [{ "bearerAuth": [] }], "responses": { "200": { "description": "Status now pending_review" }, "400": { "description": "Missing required field or document" } } }
    },
    "/api/v1/kyc/documents": {
      "get": { "tags": ["kyc"], "summary": "List uploaded documents", "security": [{ "bearerAuth": [] }], "responses": { "200": { "description": "Documents" } } },
      "post": { "tags": ["kyc"], "summary": "Register an already-uploaded object (DO Spaces object_key + sha256)", "security": [{ "bearerAuth": [] }], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DocumentRegisterRequest" } } } }, "responses": { "200": { "description": "Registered" } } }
    },
    "/api/v1/kyc/documents/upload-url": {
      "post": { "tags": ["kyc"], "summary": "Request a presigned DO Spaces PUT URL", "security": [{ "bearerAuth": [] }], "responses": { "501": { "description": "Not implemented — Spaces credentials not wired yet" } } }
    },
    "/api/v1/kyb/corporate": {
      "get": { "tags": ["kyb"], "summary": "Read corporate profile", "security": [{ "bearerAuth": [] }], "responses": { "200": { "description": "Profile", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CorporateProfile" } } } } } },
      "post": { "tags": ["kyb"], "summary": "Upsert corporate profile", "security": [{ "bearerAuth": [] }], "responses": { "200": { "description": "Updated" } } }
    },
    "/api/v1/kyb/corporate/submit": {
      "post": { "tags": ["kyb"], "summary": "Submit — requires legal name, reg number, jurisdiction, LOB, incorporation doc, and ≥1 UBO", "security": [{ "bearerAuth": [] }], "responses": { "200": { "description": "Status now pending_review" }, "400": { "description": "Missing required field / document / UBO" } } }
    },
    "/api/v1/kyb/shareholders": {
      "get": { "tags": ["kyb"], "summary": "List shareholders and UBOs", "security": [{ "bearerAuth": [] }], "responses": { "200": { "description": "Shareholders", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Shareholder" } } } } } } },
      "post": { "tags": ["kyb"], "summary": "Add a shareholder or UBO (locked once corporate is pending/approved)", "security": [{ "bearerAuth": [] }], "responses": { "200": { "description": "Added" }, "400": { "description": "Corporate already submitted — cannot mutate" } } }
    },
    "/api/v1/kyb/shareholders/{id}": {
      "delete": { "tags": ["kyb"], "summary": "Remove a shareholder (pre-submit only)", "security": [{ "bearerAuth": [] }], "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" } }], "responses": { "204": { "description": "Deleted" }, "404": { "description": "Not found" } } }
    },
    "/api/v1/balances": {
      "get": { "tags": ["wallet"], "summary": "List balances across all supported currencies", "security": [{ "bearerAuth": [] }], "responses": { "200": { "description": "Balances", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/BalanceInfo" } } } } } } }
    },
    "/api/v1/wallet/withdraw": {
      "post": { "tags": ["wallet"], "summary": "Submit a withdrawal — routed through risk-settlement + custodian", "security": [{ "bearerAuth": [] }], "responses": { "200": { "description": "Withdrawal queued" } } }
    },
    "/api/v1/wallet/deposits": {
      "get": { "tags": ["wallet"], "summary": "Deposit history", "security": [{ "bearerAuth": [] }], "responses": { "200": { "description": "Deposits" } } }
    },
    "/api/v1/wallet/withdrawals": {
      "get": { "tags": ["wallet"], "summary": "Withdrawal history", "security": [{ "bearerAuth": [] }], "responses": { "200": { "description": "Withdrawals" } } }
    },
    "/api/v1/orders": {
      "post": { "tags": ["orders"], "summary": "Submit a new order", "security": [{ "bearerAuth": [] }], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SubmitOrderRequest" } } } }, "responses": { "200": { "description": "Order submitted" } } }
    },
    "/api/v1/orders/{id}": {
      "delete": { "tags": ["orders"], "summary": "Cancel an order", "security": [{ "bearerAuth": [] }], "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" } }], "responses": { "200": { "description": "Cancel submitted" } } }
    },
    "/api/v1/markets": {
      "get": { "tags": ["markets"], "summary": "List all tradable markets", "responses": { "200": { "description": "Markets" } } }
    },
    "/api/v1/markets/tickers": {
      "get": { "tags": ["markets"], "summary": "24h tickers across all markets", "responses": { "200": { "description": "Tickers", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Ticker" } } } } } } }
    },
    "/api/v1/markets/{symbol}/orderbook": {
      "get": { "tags": ["markets"], "summary": "Order book snapshot", "parameters": [{ "name": "symbol", "in": "path", "required": true, "schema": { "type": "string" } }], "responses": { "200": { "description": "Order book" } } }
    },
    "/api/v1/markets/{symbol}/trades": {
      "get": { "tags": ["markets"], "summary": "Recent public trades", "parameters": [{ "name": "symbol", "in": "path", "required": true, "schema": { "type": "string" } }], "responses": { "200": { "description": "Trades" } } }
    },
    "/api/v1/markets/{symbol}/ticker": {
      "get": { "tags": ["markets"], "summary": "Single ticker", "parameters": [{ "name": "symbol", "in": "path", "required": true, "schema": { "type": "string" } }], "responses": { "200": { "description": "Ticker", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Ticker" } } } } } }
    },
    "/api/v1/rfq": {
      "post": { "tags": ["rfq"], "summary": "Request a two-way quote", "security": [{ "bearerAuth": [] }], "responses": { "200": { "description": "Quote" } } }
    },
    "/api/v1/rfq/{id}/accept": {
      "post": { "tags": ["rfq"], "summary": "Accept a quote before its TTL", "security": [{ "bearerAuth": [] }], "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }], "responses": { "200": { "description": "Accepted" } } }
    }
  }
}
