openapi: "3.1.0"
info:
  title: "Turnqey API"
  version: "2026.04.1"
  summary: "Cryptoasset infrastructure for Registered Investment Advisors."
  description: "Unified API surface across the Turnqey product line - Turnqey API (accounts, transactions, balances, integrity), QScore (market data, scoring, portfolios), TAIP (tax reporting), Q-Sim, ROI Calculator, Qeychain (wallet tracking), and ByAllAccounts aggregation."
  contact:
    name: "Turnqey Developer Support"
    email: "support@turnqey.xyz"
    url: "https://docs.turnqey.xyz/"
servers:
  - url: "https://api.turnqey.xyz"
    description: "Turnqey API - Production (use /v1/api/* paths)"
  - url: "https://sandbox.turnqey.xyz"
    description: "Sandbox"
tags:
  - name: "Auth"
    description: "Bearer-token issuance for the Turnqey API."
  - name: "Accounts"
    description: "Cryptoasset accounts, identity lookups, transactions, and balances."
  - name: "Integrity"
    description: "Data checksum endpoints used for partner reconciliation."
  - name: "Cryptoassets"
    description: "Market data, prices, and reference info for tracked cryptocurrencies."
  - name: "QScore"
    description: "Portfolio risk and credit-style scoring (Q, F, R scores)."
  - name: "Portfolios"
    description: "Cryptocurrency portfolio CRUD with weighted allocations."
  - name: "Status"
    description: "Service status and health probes."
  - name: "ByAllAccounts"
    description: "Aggregated financial accounts and portfolio summaries."
  - name: "TAIP"
    description: "Cryptoasset tax reporting and Form 8949 generation."
  - name: "Qeychain"
    description: "Wallet-address-based holdings, cost basis, IRS Schedule 1."
  - name: "Q-Sim"
    description: "Portfolio simulation and scenario modeling."
  - name: "ROI"
    description: "Investment ROI and benchmark projections."
security:
  - Bearer: []
paths:
  "/v1/api/token":
    post:
      operationId: "createToken"
      tags:
        - "Auth"
      summary: "Generate authentication token"
      description: "Creates a new JWT token using client credentials. Tokens are valid for one hour - call this endpoint hourly to rotate."
      requestBody:
        required: true
        content:
          "application/json":
            example:
              clientId: "tnq_client_01HVYX"
              clientSecret: "••••••••"
      responses:
        200:
          description: "Token issued"
          content:
            "application/json":
              example:
                isError: false
                message: "Success"
                result:
                  accessToken: "eyJhbGciOiJIUzI1Ni…"
                  expiresIn: 3600
                  tokenType: "Bearer"
                statusCode: 200
        401:
          description: "Invalid client credentials."
      security: []
  "/v1/api/account/collection/{collectionId}":
    get:
      operationId: "getAccountByCollection"
      tags:
        - "Accounts"
      summary: "Get account collection by ID"
      description: "Retrieves account information for a specific collection identified by its UUID."
      parameters:
        - name: "collectionId"
          in: "path"
          required: true
          description: "UUID of the account collection."
          schema:
            type: "string"
            format: "uuid"
      responses:
        200:
          description: "Account collection"
          content:
            "application/json":
              example:
                isError: false
                message: "Success"
                result:
                  accountId: "12345678-e89b-12d3-a456-426614174000"
                  collectionId: "98765432-e89b-12d3-a456-426614174000"
                  platform: "coinbase"
                  createdOn: "2024-08-01T00:00:00Z"
                statusCode: 200
        401:
          description: "Unauthorized - bearer token missing or invalid."
        404:
          description: "Collection not found."
  "/v1/api/account/identity/{identity}":
    get:
      operationId: "getAccountByIdentity"
      tags:
        - "Accounts"
      summary: "Get account by identity"
      description: "Retrieves account information for a specific identity string (e.g. external advisor identifier)."
      parameters:
        - name: "identity"
          in: "path"
          required: true
          description: "Identity string used by the partner system."
          schema:
            type: "string"
      responses:
        200:
          description: "Account record"
          content:
            "application/json":
              example:
                isError: false
                message: "Success"
                result:
                  accountId: "12345678-e89b-12d3-a456-426614174000"
                  identity: "advisor_42"
                  platform: "kraken"
                statusCode: 200
        401:
          description: "Unauthorized."
        404:
          description: "Identity not found."
  "/v1/api/account/{accountId}/transactions":
    get:
      operationId: "getAccountTransactions"
      tags:
        - "Accounts"
      summary: "Get account transactions"
      description: "Retrieves transaction history for a specific account identified by its UUID."
      parameters:
        - name: "accountId"
          in: "path"
          required: true
          description: "UUID of the account."
          schema:
            type: "string"
            format: "uuid"
        - name: "from"
          in: "query"
          required: false
          description: "Inclusive start date (ISO-8601)."
          schema:
            type: "string"
            format: "date"
        - name: "to"
          in: "query"
          required: false
          description: "Inclusive end date (ISO-8601)."
          schema:
            type: "string"
            format: "date"
      responses:
        200:
          description: "Transaction history"
          content:
            "application/json":
              example:
                isError: false
                message: "Success"
                result:
                  transactions:
                    - transactionId: "tx_01HVYX"
                      symbol: "BTC"
                      direction: "buy"
                      amount: "0.5"
                      price: "49000.00"
                      timestamp: "2024-09-12T15:04:05Z"
                statusCode: 200
        401:
          description: "Unauthorized."
  "/v1/api/account/{accountId}/balances":
    get:
      operationId: "getAccountBalances"
      tags:
        - "Accounts"
      summary: "Get account balances"
      description: "Retrieves current balances for a specific account identified by its UUID."
      parameters:
        - name: "accountId"
          in: "path"
          required: true
          description: "UUID of the account."
          schema:
            type: "string"
            format: "uuid"
        - name: "forceRefresh"
          in: "query"
          required: false
          description: "Force a refresh from the source if cached data is older than 30 minutes."
          schema:
            type: "boolean"
      responses:
        200:
          description: "Account balances"
          content:
            "application/json":
              example:
                isError: false
                message: "Success"
                result:
                  balances:
                    - symbol: "BTC"
                      balance: "0.5"
                      costBasis: "24500.00"
                      marketValue: "25000.00"
                      realizedGainLoss: "500.00"
                      unrealizedGainLoss: "500.00"
                  lastUpdatedOn: "2024-09-12T15:04:05Z"
                  marketValue: "25000.00"
                  realizedGainLoss: "500.00"
                  unrealizedGainLoss: "500.00"
                statusCode: 200
        401:
          description: "Unauthorized."
        422:
          description: "Unprocessable entity."
  "/v1/api/checksum":
    get:
      operationId: "getChecksum"
      tags:
        - "Integrity"
      summary: "Get data checksum"
      description: "Retrieves a checksum for data verification, filtered by platform, collection, or identity."
      parameters:
        - name: "platform"
          in: "query"
          required: false
          description: "Filter by source platform (e.g. coinbase, kraken)."
          schema:
            type: "string"
        - name: "collectionId"
          in: "query"
          required: false
          description: "Filter by account collection."
          schema:
            type: "string"
            format: "uuid"
        - name: "identity"
          in: "query"
          required: false
          description: "Filter by external identity string."
          schema:
            type: "string"
      responses:
        200:
          description: "Checksum payload"
          content:
            "application/json":
              example:
                isError: false
                message: "Success"
                result:
                  checksum: "f3a1c0b9d4e8…"
                  computedAt: "2024-09-12T15:04:05Z"
                  rowCount: 4218
                statusCode: 200
        401:
          description: "Unauthorized."
  "/api/crypto-data/{symbols}":
    get:
      operationId: "getCryptoData"
      tags:
        - "Cryptoassets"
      summary: "Get cryptocurrency market data"
      description: "Fetch current market data for the specified cryptocurrencies. `symbols` is a comma-separated list of CoinGecko IDs (e.g. `bitcoin,ethereum,solana`)."
      parameters:
        - name: "symbols"
          in: "path"
          required: true
          description: "Comma-separated list of cryptocurrency symbols."
          schema:
            type: "string"
      responses:
        200:
          description: "Array of market data records"
          content:
            "application/json":
              example:
                - id: 1
                  symbol: "BTC"
                  name: "Bitcoin"
                  price: 64321.12
                  marketCap: 1268000000000
                  marketCapRank: 1
                  circulatingSupply: 19712000
                  totalSupply: 21000000
                  maxSupply: 21000000
                  volume24h: 32100000000
                  volatility: 0.62
                  communityScore: 87.4
                  developerScore: 92.1
                  liquidityScore: 95
                  publicInterestScore: 88.6
                  lastUpdated: "2024-09-12T15:04:05Z"
        429:
          description: "Rate limit exceeded."
        500:
          description: "Server error."
  "/api/calculate-portfolio-score":
    post:
      operationId: "calculatePortfolioScore"
      tags:
        - "QScore"
      summary: "Calculate portfolio Q Score"
      description: "Calculate the overall Q Score, F Score, and R Score for a cryptocurrency portfolio described by per-asset allocation percentages."
      requestBody:
        required: true
        content:
          "application/json":
            example:
              btcAllocation: 60
              ethAllocation: 30
              solAllocation: 10
      responses:
        200:
          description: "Portfolio Q Score result"
          content:
            "application/json":
              example:
                qScore: 82.4
                fScore: 78.1
                rScore: 64.9
                allocation:
                  BTC: 60
                  ETH: 30
                  SOL: 10
                breakdown:
                  BTC: 88
                  ETH: 79
                  SOL: 65
        400:
          description: "Missing score data or invalid allocations."
        500:
          description: "Server error."
  "/api/scores":
    get:
      operationId: "getScores"
      tags:
        - "QScore"
      summary: "Get all cryptocurrency Q scores"
      description: "Retrieve Q scores, F scores, and R scores for all tracked cryptocurrencies."
      responses:
        200:
          description: "Per-asset score table"
          content:
            "application/json":
              example:
                - symbol: "BTC"
                  qScore: 88
                  fScore: 90.1
                  rScore: 70.2
                - symbol: "ETH"
                  qScore: 79
                  fScore: 81.5
                  rScore: 64
        500:
          description: "Server error."
  "/api/status":
    get:
      operationId: "getStatus"
      tags:
        - "Status"
      summary: "Get API status and health information"
      description: "Check API connectivity, rate limits, and system status."
      responses:
        200:
          description: "Service status"
          content:
            "application/json":
              example:
                status: "ok"
                uptimeSeconds: 481239
                rateLimitRemaining: 1000
                version: "2026.04.1"
  "/api/portfolios":
    get:
      operationId: "listPortfolios"
      tags:
        - "Portfolios"
      summary: "Get all portfolios"
      description: "Retrieve all user portfolios with their allocations."
      responses:
        200:
          description: "Portfolio list"
          content:
            "application/json":
              example:
                - id: "pf_01HVYX"
                  name: "Conservative Crypto"
                  allocations:
                    BTC: 70
                    ETH: 30
        401:
          description: "Unauthorized."
    post:
      operationId: "createPortfolio"
      tags:
        - "Portfolios"
      summary: "Create a new portfolio"
      description: "Create a new cryptocurrency portfolio."
      requestBody:
        required: true
        content:
          "application/json":
            example:
              name: "Aggressive Growth"
              allocations:
                BTC: 50
                ETH: 30
                SOL: 20
      responses:
        201:
          description: "Created"
          content:
            "application/json":
              example:
                id: "pf_01HVZY"
                name: "Aggressive Growth"
                allocations:
                  BTC: 50
                  ETH: 30
                  SOL: 20
        400:
          description: "Allocations must sum to 100."
  "/api/portfolios/{id}":
    get:
      operationId: "getPortfolio"
      tags:
        - "Portfolios"
      summary: "Get portfolio by ID"
      description: "Retrieve a specific portfolio with its allocations."
      parameters:
        - name: "id"
          in: "path"
          required: true
          description: "Portfolio identifier."
          schema:
            type: "string"
      responses:
        200:
          description: "Portfolio detail"
          content:
            "application/json":
              example:
                id: "pf_01HVYX"
                name: "Conservative Crypto"
                allocations:
                  BTC: 70
                  ETH: 30
                qScore: 82.4
        404:
          description: "Portfolio not found."
    put:
      operationId: "updatePortfolio"
      tags:
        - "Portfolios"
      summary: "Update portfolio"
      description: "Update an existing portfolio (replaces allocations)."
      parameters:
        - name: "id"
          in: "path"
          required: true
          description: "Portfolio identifier."
          schema:
            type: "string"
      requestBody:
        required: true
        content:
          "application/json":
            example:
              name: "Conservative Crypto v2"
              allocations:
                BTC: 65
                ETH: 35
      responses:
        200:
          description: "Updated"
          content:
            "application/json":
              example:
                id: "pf_01HVYX"
                name: "Conservative Crypto v2"
                allocations:
                  BTC: 65
                  ETH: 35
        404:
          description: "Portfolio not found."
    delete:
      operationId: "deletePortfolio"
      tags:
        - "Portfolios"
      summary: "Delete portfolio"
      description: "Delete a portfolio and its allocations."
      parameters:
        - name: "id"
          in: "path"
          required: true
          description: "Portfolio identifier."
          schema:
            type: "string"
      responses:
        204:
          description: "Portfolio deleted."
        404:
          description: "Portfolio not found."
  "/portfolio/summary":
    get:
      operationId: "getPortfolioSummary"
      tags:
        - "ByAllAccounts"
      summary: "Portfolio summary"
      description: "Returns an aggregated portfolio summary across linked institutions."
      parameters:
        - name: "account_id"
          in: "query"
          required: false
          description: "Restrict the summary to a single linked account."
          schema:
            type: "string"
      responses:
        200:
          description: "Aggregated portfolio summary"
          content:
            "application/json":
              example:
                btc_balance: "2.847 BTC"
                usd_value: "$274,318"
                exchanges:
                  - "Coinbase"
                  - "Kraken"
                sync_status: "live"
        401:
          description: "Missing or invalid bearer token."
  "/qscore/evaluate":
    post:
      operationId: "evaluateQScore"
      tags:
        - "QScore"
      summary: "Evaluate a QScore for a linked account"
      description: "Compute a QScore for a portfolio snapshot already linked via ByAllAccounts."
      requestBody:
        required: true
        content:
          "application/json":
            example:
              account_id: "acct_01HVYX"
              as_of_date: "2026-04-15"
      responses:
        200:
          description: "QScore result"
          content:
            "application/json":
              example:
                score: 812
                grade: "A+"
                btc_weight: "64%"
                risk_level: "aggressive"
        400:
          description: "Malformed request body."
  "/taip/gains-report":
    get:
      operationId: "getGainsReport"
      tags:
        - "TAIP"
      summary: "Gains report"
      description: "Realized and unrealized gains for an account, ready for Form 8949."
      parameters:
        - name: "tax_year"
          in: "query"
          required: true
          description: "Tax year (e.g. 2026)."
          schema:
            type: "integer"
      responses:
        200:
          description: "Gains report"
          content:
            "application/json":
              example:
                asset: "BTC"
                net_gains: "$98,140"
                transactions: 312
                form_8949: "ready"
  "/qeychain/wallets":
    get:
      operationId: "listWallets"
      tags:
        - "Qeychain"
      summary: "List tracked wallets"
      description: "Returns a paginated list of wallets tracked under your organization."
      parameters:
        - name: "limit"
          in: "query"
          required: false
          description: "Page size (default 50, max 200)."
          schema:
            type: "integer"
        - name: "cursor"
          in: "query"
          required: false
          description: "Cursor returned by a previous page."
          schema:
            type: "string"
      responses:
        200:
          description: "Paginated wallet list"
          content:
            "application/json":
              example:
                data:
                  - address: "bc1qxy2k..."
                    chain: "bitcoin"
                    btc_holdings: "1.204 BTC"
                    cost_basis: "$51,200"
                    unrealized_pnl: "+$64,918"
                next_cursor: null
  "/qsim/simulate":
    post:
      operationId: "runSimulation"
      tags:
        - "Q-Sim"
      summary: "Run a portfolio simulation"
      description: "Run scenario projections (bull/base/bear) over a target horizon."
      requestBody:
        required: true
        content:
          "application/json":
            example:
              portfolio:
                - symbol: "BTC"
                  weight: 0.6
                - symbol: "ETH"
                  weight: 0.4
              horizon_years: 5
              scenarios:
                - "bull"
                - "base"
                - "bear"
      responses:
        200:
          description: "Simulation result"
          content:
            "application/json":
              example:
                scenarios:
                  - name: "bull"
                    cagr: "21.4%"
                    terminal_usd: "$264,118"
                  - name: "base"
                    cagr: "9.2%"
                    terminal_usd: "$155,402"
                  - name: "bear"
                    cagr: "-3.1%"
                    terminal_usd: "$85,431"
  "/roi/projection":
    get:
      operationId: "getRoiProjection"
      tags:
        - "ROI"
      summary: "ROI projection"
      description: "Single-shot ROI projection given a principal and horizon."
      parameters:
        - name: "principal_usd"
          in: "query"
          required: true
          description: "Principal amount in USD."
          schema:
            type: "number"
        - name: "horizon_years"
          in: "query"
          required: true
          description: "Investment horizon in years."
          schema:
            type: "integer"
      responses:
        200:
          description: "ROI projection"
          content:
            "application/json":
              example:
                principal_usd: 100000
                horizon_years: 10
                terminal_usd: "$248,400"
                cagr: "9.5%"
                benchmark_diff: "+2.1%"
components:
  securitySchemes:
    Bearer:
      type: "http"
      scheme: "bearer"
      bearerFormat: "JWT"
      description: "JWT bearer token issued by POST /v1/api/token."
