> ## Documentation Index
> Fetch the complete documentation index at: https://finance.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Account Positions

> Retrieve all open positions for a specific trading account including quantity, cost basis, current market value, and unrealized profit/loss. Track portfolio holdings and performance metrics in real-time.

**Use Case:** Display a portfolio summary with current positions, P&L, and percentage gains/losses for each holding.


export default function OAuthLoginButton({scopes = "user:information account:information order:execution order:information position:information market:information calendar:information options:information analytics:information market:supplemental", responseType = "token", buttonText = "Authenticate with OAuth", showStatus = true}) {
  const [isDarkMode, setIsDarkMode] = useState(false);
  useEffect(() => {
    const checkDarkMode = () => {
      const isDark = document.documentElement.classList.contains("dark") || document.documentElement.getAttribute("data-theme") === "dark" || document.body.classList.contains("dark") || document.body.getAttribute("data-theme") === "dark";
      setIsDarkMode(isDark);
    };
    checkDarkMode();
    const htmlObserver = new MutationObserver(checkDarkMode);
    const bodyObserver = new MutationObserver(checkDarkMode);
    htmlObserver.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ["class", "data-theme", "style"]
    });
    bodyObserver.observe(document.body, {
      attributes: true,
      attributeFilter: ["class", "data-theme", "style"]
    });
    const interval = setInterval(checkDarkMode, 1000);
    return () => {
      htmlObserver.disconnect();
      bodyObserver.disconnect();
      clearInterval(interval);
    };
  }, []);
  const styles = useMemo(() => ({
    statusContainer: {
      marginBottom: "1.5rem",
      padding: "0.875rem 1rem",
      backgroundColor: isDarkMode ? "rgba(11, 170, 94, 0.08)" : "rgba(11, 170, 94, 0.06)",
      border: isDarkMode ? "1px solid rgba(11, 170, 94, 0.25)" : "1px solid rgba(11, 170, 94, 0.25)",
      borderRadius: "10px"
    },
    statusContent: {
      display: "flex",
      alignItems: "center",
      justifyContent: "space-between",
      gap: "1rem",
      flexWrap: "wrap"
    },
    statusLeft: {
      display: "flex",
      alignItems: "center",
      gap: "1rem",
      flexWrap: "wrap"
    },
    statusBadge: {
      display: "flex",
      alignItems: "center",
      gap: "0.5rem"
    },
    checkmark: {
      color: isDarkMode ? "#0BAA5E" : "#0BAA5E",
      fontSize: "1.25rem",
      fontWeight: "bold"
    },
    statusText: {
      color: isDarkMode ? "#0BAA5E" : "#0BAA5E",
      fontWeight: "600",
      fontSize: "0.95rem"
    },
    expiryText: {
      color: isDarkMode ? "#a1a1aa" : "#71717a",
      fontSize: "0.8125rem"
    },
    logoutButton: {
      padding: "0.375rem 0.75rem",
      backgroundColor: "transparent",
      color: isDarkMode ? "#e4e4e7" : "#3f3f46",
      border: isDarkMode ? "1px solid rgba(255, 255, 255, 0.1)" : "1px solid rgba(0, 0, 0, 0.1)",
      borderRadius: "6px",
      cursor: "pointer",
      fontSize: "0.8125rem",
      fontWeight: "500",
      transition: "background-color 0.15s ease"
    },
    logoutButtonHover: {
      backgroundColor: isDarkMode ? "rgba(255, 255, 255, 0.05)" : "rgba(0, 0, 0, 0.04)"
    },
    buttonContainer: {
      marginBottom: "1.5rem",
      display: "flex",
      alignItems: "center",
      justifyContent: "space-between",
      gap: "1rem",
      padding: "0.875rem 1rem",
      backgroundColor: "transparent",
      border: isDarkMode ? "1px solid rgba(255, 255, 255, 0.1)" : "1px solid rgba(0, 0, 0, 0.08)",
      borderRadius: "10px"
    },
    loginButton: {
      display: "inline-flex",
      alignItems: "center",
      gap: "0.5rem",
      padding: "0.5rem 0.875rem",
      backgroundColor: "#0BAA5E",
      color: "white",
      border: "1px solid #0BAA5E",
      borderRadius: "8px",
      cursor: "pointer",
      fontSize: "0.8125rem",
      fontWeight: "500",
      lineHeight: "1.25rem",
      transition: "background-color 0.15s ease, border-color 0.15s ease",
      whiteSpace: "nowrap",
      flexShrink: 0
    },
    loginButtonHover: {
      backgroundColor: "#098F4E",
      borderColor: "#098F4E"
    },
    lockIcon: {
      width: "14px",
      height: "14px",
      flexShrink: 0
    },
    helpText: {
      margin: 0,
      fontSize: "0.8125rem",
      color: isDarkMode ? "#9ca3af" : "#6b7280",
      lineHeight: "1.5",
      flex: 1
    },
    modalDialog: {
      border: "none",
      backgroundColor: "transparent",
      padding: 0,
      margin: 0,
      maxWidth: "100%",
      maxHeight: "100%"
    },
    modalContent: {
      backgroundColor: isDarkMode ? "#0f0f10" : "#ffffff",
      border: isDarkMode ? "1px solid rgba(255, 255, 255, 0.08)" : "1px solid rgba(0, 0, 0, 0.08)",
      borderRadius: "12px",
      padding: "1.5rem",
      maxWidth: "500px",
      width: "100%",
      boxShadow: isDarkMode ? "0 20px 25px -5px rgba(0, 0, 0, 0.5), 0 10px 10px -5px rgba(0, 0, 0, 0.3)" : "0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)"
    },
    modalHeader: {
      display: "flex",
      justifyContent: "space-between",
      alignItems: "center",
      marginBottom: "1.5rem"
    },
    modalTitle: {
      margin: 0,
      fontSize: "1.125rem",
      fontWeight: "600",
      color: isDarkMode ? "#fafafa" : "#0a0a0a"
    },
    closeButton: {
      background: "none",
      border: "none",
      fontSize: "1.5rem",
      lineHeight: 1,
      color: isDarkMode ? "#a1a1aa" : "#71717a",
      cursor: "pointer",
      padding: "0",
      width: "2rem",
      height: "2rem",
      display: "flex",
      alignItems: "center",
      justifyContent: "center",
      borderRadius: "6px",
      transition: "color 0.15s ease, background-color 0.15s ease"
    },
    form: {
      display: "flex",
      flexDirection: "column",
      gap: "1rem"
    },
    formGroup: {
      display: "flex",
      flexDirection: "column",
      gap: "0.5rem"
    },
    label: {
      fontSize: "0.8125rem",
      fontWeight: "500",
      color: isDarkMode ? "#e4e4e7" : "#3f3f46"
    },
    input: {
      padding: "0.5rem 0.75rem",
      border: isDarkMode ? "1px solid rgba(255, 255, 255, 0.1)" : "1px solid rgba(0, 0, 0, 0.1)",
      borderRadius: "8px",
      fontSize: "0.875rem",
      color: isDarkMode ? "#fafafa" : "#0a0a0a",
      backgroundColor: isDarkMode ? "rgba(255, 255, 255, 0.03)" : "#ffffff",
      transition: "border-color 0.15s ease, box-shadow 0.15s ease",
      outline: "none"
    },
    inputHelp: {
      margin: 0,
      fontSize: "0.75rem",
      color: isDarkMode ? "#a1a1aa" : "#71717a"
    },
    link: {
      color: "#0BAA5E",
      textDecoration: "none",
      fontWeight: "500"
    },
    errorBox: {
      padding: "0.75rem",
      backgroundColor: isDarkMode ? "rgba(220, 38, 38, 0.1)" : "rgba(220, 38, 38, 0.05)",
      border: isDarkMode ? "1px solid rgba(220, 38, 38, 0.3)" : "1px solid rgba(220, 38, 38, 0.2)",
      borderRadius: "8px",
      color: isDarkMode ? "#fca5a5" : "#dc2626",
      fontSize: "0.8125rem"
    },
    modalFooter: {
      display: "flex",
      gap: "0.5rem",
      justifyContent: "flex-end",
      marginTop: "0.5rem"
    },
    cancelButton: {
      padding: "0.5rem 0.875rem",
      backgroundColor: "transparent",
      color: isDarkMode ? "#e4e4e7" : "#3f3f46",
      border: isDarkMode ? "1px solid rgba(255, 255, 255, 0.1)" : "1px solid rgba(0, 0, 0, 0.1)",
      borderRadius: "8px",
      cursor: "pointer",
      fontSize: "0.8125rem",
      fontWeight: "500",
      lineHeight: "1.25rem",
      transition: "background-color 0.15s ease"
    },
    submitButton: {
      padding: "0.5rem 0.875rem",
      backgroundColor: "#0BAA5E",
      color: "white",
      border: "1px solid #0BAA5E",
      borderRadius: "8px",
      cursor: "pointer",
      fontSize: "0.8125rem",
      fontWeight: "500",
      lineHeight: "1.25rem",
      transition: "background-color 0.15s ease, border-color 0.15s ease"
    },
    submitButtonDisabled: {
      opacity: 0.6,
      cursor: "not-allowed"
    },
    redirectUrlContainer: {
      display: "flex",
      alignItems: "center",
      gap: "0.5rem",
      padding: "0.5rem 0.75rem",
      backgroundColor: isDarkMode ? "rgba(255, 255, 255, 0.03)" : "rgba(0, 0, 0, 0.03)",
      border: isDarkMode ? "1px solid rgba(255, 255, 255, 0.08)" : "1px solid rgba(0, 0, 0, 0.08)",
      borderRadius: "8px",
      fontSize: "0.8125rem",
      fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace"
    },
    redirectUrlText: {
      flex: 1,
      color: isDarkMode ? "#a1a1aa" : "#71717a",
      overflow: "hidden",
      textOverflow: "ellipsis",
      whiteSpace: "nowrap"
    },
    copyButton: {
      padding: "0.25rem 0.625rem",
      backgroundColor: "transparent",
      color: isDarkMode ? "#e4e4e7" : "#3f3f46",
      border: isDarkMode ? "1px solid rgba(255, 255, 255, 0.1)" : "1px solid rgba(0, 0, 0, 0.1)",
      borderRadius: "6px",
      cursor: "pointer",
      fontSize: "0.75rem",
      fontWeight: "500",
      transition: "background-color 0.15s ease, border-color 0.15s ease",
      whiteSpace: "nowrap"
    },
    copyButtonHover: {
      backgroundColor: isDarkMode ? "rgba(255, 255, 255, 0.05)" : "rgba(0, 0, 0, 0.04)"
    },
    copyButtonCopied: {
      backgroundColor: "#0BAA5E",
      color: "white",
      border: "1px solid #0BAA5E"
    }
  }), [isDarkMode]);
  const [isModalOpen, setIsModalOpen] = useState(false);
  const [clientId, setClientId] = useState("");
  const [isLoading, setIsLoading] = useState(false);
  const [hasValidToken, setHasValidToken] = useState(false);
  const [tokenSource, setTokenSource] = useState(null);
  const [timeRemaining, setTimeRemaining] = useState(0);
  const [error, setError] = useState("");
  const [copied, setCopied] = useState(false);
  const redirectUrl = typeof window !== "undefined" ? window.location.origin + "/api-reference" : "";
  useEffect(() => {
    updateTokenState();
    if (window.AriesOAuth) {
      const lastClientId = window.AriesOAuth.getLastClientId();
      setClientId(lastClientId);
    }
    const handleTokenUpdate = () => {
      updateTokenState();
    };
    const handleOAuthError = event => {
      setError(event.detail.description || "Authentication failed");
      setIsLoading(false);
    };
    const handleOAuthSuccess = () => {
      setIsModalOpen(false);
      setIsLoading(false);
      setError("");
    };
    window.addEventListener("tokenStatusChanged", handleTokenUpdate);
    window.addEventListener("tokenUpdated", handleTokenUpdate);
    window.addEventListener("tokenCleared", handleTokenUpdate);
    window.addEventListener("oauthError", handleOAuthError);
    window.addEventListener("oauthSuccess", handleOAuthSuccess);
    const interval = setInterval(() => {
      if (hasValidToken) {
        updateTokenState();
      }
    }, 1000);
    return () => {
      window.removeEventListener("tokenStatusChanged", handleTokenUpdate);
      window.removeEventListener("tokenUpdated", handleTokenUpdate);
      window.removeEventListener("tokenCleared", handleTokenUpdate);
      window.removeEventListener("oauthError", handleOAuthError);
      window.removeEventListener("oauthSuccess", handleOAuthSuccess);
      clearInterval(interval);
    };
  }, [hasValidToken]);
  const updateTokenState = useCallback(() => {
    if (window.AriesOAuth) {
      const isValid = window.AriesOAuth.isAuthenticated();
      const remaining = window.AriesOAuth.getTimeRemaining();
      const knownExpiry = window.AriesOAuth.hasKnownExpiry ? window.AriesOAuth.hasKnownExpiry() : false;
      const source = window.AriesOAuth.getTokenSource ? window.AriesOAuth.getTokenSource() : null;
      setHasValidToken(isValid);
      setTokenSource(source);
      setTimeRemaining(knownExpiry && typeof remaining === "number" ? remaining : null);
    }
  }, []);
  const handleLogin = () => {
    setError("");
    setIsModalOpen(true);
  };
  const handleLogout = () => {
    if (window.AriesOAuth) {
      window.AriesOAuth.logout();
    }
    try {
      const cfg = window.AriesOAuth?.config;
      const tokenKey = cfg?.TOKEN_KEY ?? "aries_access_token";
      const expiryKey = cfg?.TOKEN_EXPIRY_KEY ?? "aries_token_timestamp";
      const sourceKey = cfg?.TOKEN_SOURCE_KEY ?? "aries_token_source";
      localStorage.removeItem(tokenKey);
      localStorage.removeItem(expiryKey);
      localStorage.removeItem(sourceKey);
    } catch (_) {}
    setHasValidToken(false);
    setTimeRemaining(null);
  };
  const handleOAuthRedirect = async e => {
    e.preventDefault();
    if (!clientId.trim()) {
      setError("Please enter your Client ID");
      return;
    }
    setIsLoading(true);
    try {
      if (window.AriesOAuth) {
        window.AriesOAuth.saveClientId(clientId.trim());
      }
      const returnPath = window.location.pathname + window.location.search + window.location.hash;
      sessionStorage.setItem("oauth_return_path", returnPath);
      const pkce = await window.AriesOAuth.generatePKCE();
      const authEndpoint = window.AriesOAuth?.config?.AUTH_ENDPOINT || "https://app.aries.com/oauth2/authorize";
      const authUrl = new URL(authEndpoint);
      authUrl.searchParams.set("client_id", clientId.trim());
      authUrl.searchParams.set("redirect_uri", redirectUrl);
      authUrl.searchParams.set("response_type", "code");
      authUrl.searchParams.set("code_challenge", pkce.challenge);
      authUrl.searchParams.set("code_challenge_method", "S256");
      authUrl.searchParams.set("scope", scopes);
      authUrl.searchParams.set("state", generateState());
      const fullAuthUrl = authUrl.toString();
      setTimeout(() => {
        window.location.href = fullAuthUrl;
      }, 100);
    } catch (error) {
      setError("Failed to initiate OAuth flow: " + error.message);
      setIsLoading(false);
    }
  };
  const generateState = () => {
    return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
  };
  const formatTimeRemaining = seconds => {
    const minutes = Math.floor(seconds / 60);
    const secs = seconds % 60;
    return `${minutes}m ${secs}s`;
  };
  const copyRedirectUrl = () => {
    navigator.clipboard.writeText(redirectUrl).then(() => {
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    }).catch(() => {});
  };
  const oauthDialogRef = useRef(null);
  const isLoadingRef = useRef(false);
  isLoadingRef.current = isLoading;
  const closeModal = useCallback(() => {
    if (!isLoading) {
      setIsModalOpen(false);
      setError("");
      setCopied(false);
    }
  }, [isLoading]);
  useEffect(() => {
    const el = oauthDialogRef.current;
    if (!el) return;
    if (isModalOpen) {
      if (!el.open) el.showModal();
    } else {
      if (el.open) el.close();
    }
  }, [isModalOpen]);
  useEffect(() => {
    const el = oauthDialogRef.current;
    if (!el) return;
    const onClose = () => {
      setIsModalOpen(false);
      setError("");
      setCopied(false);
    };
    const onCancel = e => {
      if (isLoadingRef.current) e.preventDefault();
    };
    el.addEventListener("close", onClose);
    el.addEventListener("cancel", onCancel);
    return () => {
      el.removeEventListener("close", onClose);
      el.removeEventListener("cancel", onCancel);
    };
  }, [hasValidToken, showStatus]);
  if (hasValidToken && showStatus && tokenSource === "oauth") {
    return <div style={styles.statusContainer}>
        <div style={styles.statusContent}>
          <div style={styles.statusLeft}>
            <div style={styles.statusBadge}>
              <span style={styles.checkmark}>✓</span>
              <span style={styles.statusText}>Authenticated</span>
            </div>
            {typeof timeRemaining === "number" ? <span style={styles.expiryText}>
                Expires in {formatTimeRemaining(timeRemaining)}
              </span> : null}
          </div>
          <button onClick={handleLogout} style={styles.logoutButton} onMouseEnter={e => e.target.style.backgroundColor = styles.logoutButtonHover.backgroundColor} onMouseLeave={e => e.target.style.backgroundColor = styles.logoutButton.backgroundColor}>
            Logout
          </button>
        </div>
      </div>;
  }
  const onDialogClick = e => {
    if (e.target === e.currentTarget) {
      closeModal();
    }
  };
  const modalMarkup = <dialog ref={oauthDialogRef} className="aries-oauth-dialog" data-oauth-theme={isDarkMode ? "dark" : "light"} style={styles.modalDialog} onClick={onDialogClick}>
      <div style={styles.modalContent} onClick={e => e.stopPropagation()}>
        <div style={styles.modalHeader}>
          <h3 style={styles.modalTitle}>OAuth Authentication</h3>
          <button type="button" onClick={closeModal} style={styles.closeButton} disabled={isLoading}>
            ×
          </button>
        </div>

        <form onSubmit={handleOAuthRedirect} style={styles.form}>
          <div style={styles.formGroup}>
            <label htmlFor="clientId" style={styles.label}>
              Client ID
            </label>
            <input id="clientId" type="text" value={clientId} onChange={e => setClientId(e.target.value)} placeholder="Enter your OAuth Client ID" style={styles.input} disabled={isLoading} />
            <p style={styles.inputHelp}>
              Get your Client ID from{" "}
              <a href="https://app.aries.com/client-center/api" target="_blank" rel="noopener noreferrer" style={styles.link}>
                Client Center / Manage Account → API
              </a>
            </p>
          </div>

          <div style={styles.formGroup}>
            <label style={styles.label}>Redirect URL</label>
            <div style={styles.redirectUrlContainer}>
              <span style={styles.redirectUrlText} title={redirectUrl}>
                {redirectUrl}
              </span>
              <button type="button" onClick={copyRedirectUrl} style={{
    ...styles.copyButton,
    ...copied ? styles.copyButtonCopied : {}
  }} onMouseEnter={e => {
    if (!copied) {
      e.currentTarget.style.backgroundColor = styles.copyButtonHover.backgroundColor;
    }
  }} onMouseLeave={e => {
    if (!copied) {
      e.currentTarget.style.backgroundColor = styles.copyButton.backgroundColor;
    }
  }}>
                {copied ? "✓ Copied" : "Copy"}
              </button>
            </div>
            <p style={styles.inputHelp}>
              Add this URL to your OAuth client's allowed redirect URIs
            </p>
          </div>

          {error && <div style={styles.errorBox}>{error}</div>}

          <div style={styles.modalFooter}>
            <button type="button" onClick={closeModal} style={styles.cancelButton} disabled={isLoading}>
              Cancel
            </button>
            <button type="submit" style={{
    ...styles.submitButton,
    ...isLoading ? styles.submitButtonDisabled : {}
  }} disabled={isLoading}>
              {isLoading ? "Redirecting..." : "Initiate Login"}
            </button>
          </div>
        </form>
      </div>
    </dialog>;
  return <>
      {}
      <div style={styles.buttonContainer}>
        <div style={styles.helpText}>
          <strong style={{
    color: isDarkMode ? "#e5e7eb" : "#374151"
  }}>
            Quick Auth:
          </strong>{" "}
          Authenticate once to auto-fill your Bearer token across all API
          endpoints
        </div>
        <button onClick={handleLogin} style={styles.loginButton} onMouseEnter={e => {
    e.currentTarget.style.backgroundColor = styles.loginButtonHover.backgroundColor;
    e.currentTarget.style.borderColor = styles.loginButtonHover.borderColor;
  }} onMouseLeave={e => {
    e.currentTarget.style.backgroundColor = styles.loginButton.backgroundColor;
    e.currentTarget.style.borderColor = styles.loginButton.borderColor;
  }}>
          <svg style={styles.lockIcon} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
            <path d="M7 11V7a5 5 0 0 1 10 0v4" />
          </svg>
          {buttonText}
        </button>
      </div>

      {modalMarkup}
    </>;
}

<OAuthLoginButton />

## Demo response (all fields)

Position quantities and prices are returned as **decimal strings** to preserve precision. The sample below includes every JSON field the response object can contain.

```json theme={null}
{
  "positions": [
    {
      "accountId": "TEST-ACCOUNT-001",
      "symbol": "AAPL",
      "securityType": "EQUITY",
      "quantity": "100",
      "averagePrice": "170.25",
      "price": "175.40",
      "todayRealizedPnL": "0",
      "unrealizedPl": "515.00",
      "todayPl": "125.00",
      "marketValue": "17540.00",
      "costBasis": "17025.00",
      "createdAt": "2026-01-10T16:00:00Z"
    }
  ]
}
```


## OpenAPI

````yaml openapi.json GET /v1/accounts/{id}/positions
openapi: 3.0.3
info:
  contact:
    email: dev@aries.com
    name: Aries Financial
  description: >-
    OpenAPI Specification for the Aries trading platform API.


    # Authentication


    Learn how to authenticate with the Aries API using OAuth2 and manage access
    tokens in your SDK.


    ## Overview


    The Aries API uses **OAuth2 with Bearer tokens** (JWT format) for
    authentication. All API requests require a valid access token in the
    `Authorization` header:


    ```

    Authorization: Bearer <access_token>

    ```


    ## Providing Client ID and Client Secret (SDK)


    When using the generated SDK, provide your **Client ID** and **Client
    Secret** when you create the API client (e.g. in the constructor or security
    options). The SDK will use these to obtain and refresh the access token
    internally; you do not need to manage tokens yourself.


    Obtain your OAuth2 credentials from the Aries platform (e.g. Client Center /
    Manage Account at https://app.aries.com):


    - **Client ID** – Your application identifier (pass to SDK client)

    - **Client Secret** – Your application secret key (pass to SDK client; use
    PKCE for public clients where secret cannot be stored)


    ## Authentication Flow


    ### 1. Authorization Code Flow


    For server-side or confidential clients:


    1. **Redirect the user** to the authorization URL to sign in and consent:
     - **URL:** `https://app.aries.com/oauth2/authorize`
     - **Query params:** `response_type=code`, `client_id`, `redirect_uri`, `scope`, `state`

    2. **Exchange the code for tokens** (after user is redirected back with
    `?code=.`):
     - **POST** `https://api.aries.com/v1/oauth2/token`
     - **Body:** `grant_type=authorization_code`, `code`, `redirect_uri`, `client_id`, `client_secret`
     - Response includes `access_token` and `refresh_token`

    3. **Call the API** with the access token: `Authorization: Bearer
    <access_token>`


    ### 2. PKCE Flow


    For SPAs and mobile apps (public clients that cannot store `client_secret`):


    1. Generate a **code_verifier** (random string) and **code_challenge** =
    BASE64URL(SHA256(code_verifier)).

    2. **Redirect the user** to `https://app.aries.com/oauth2/authorize` with
    `code_challenge`, `code_challenge_method=S256`, plus `client_id`,
    `redirect_uri`, `scope`, `state`.

    3. **Exchange the code** at POST `https://api.aries.com/v1/oauth2/token`
    with `grant_type=authorization_code`, `code`, `redirect_uri`, `client_id`,
    `code_verifier` (no client_secret).

    4. Use the returned `access_token` as Bearer.


    ### 3. MFA Verification


    If the user has MFA enabled, the authorize step may return `is_mfa: true`
    and a `next_step_auth_id`. Call **POST**
    `https://api.aries.com/v1/oauth2/authorize/mfa` with `next_step_auth_id` and
    `verification_code` (6-digit code). Then continue with **POST**
    `/v1/oauth2/authorize/confirm` to get the authorization code, and exchange
    it at `/v1/oauth2/token`.


    ## Token Management


    - **Refresh when expired:** POST `https://api.aries.com/v1/oauth2/token`
    with `grant_type=refresh_token`, `client_id`, `client_secret`,
    `refresh_token`.

    - **Using a Bearer token directly:** If you already have an access token,
    set the header `Authorization: Bearer <access_token>` on every request. The
    SDK can accept a pre-obtained token and use it until it expires.


    ## OAuth2 Scopes


    Request only the scopes your application needs. Available scopes:


    | Scope | Description |

    |-------|-------------|

    | `user:information` | View user profile and personal details |

    | `account:information` | View account balances, positions, and transaction
    history |

    | `order:execution` | Place, modify, and cancel orders |

    | `order:information` | View order history and status |

    | `position:information` | View current positions and holdings |

    | `market:information` | Access live and historical market data |

    | `calendar:information` | Access earnings, economic, and market schedule
    data |

    | `options:information` | Access options chains and expiration data |

    | `analytics:information` | View analytics, ratings, and market insights |

    | `market:supplemental` | News, company profiles, financials, filings, ETF
    data, technical analysis |


    Specify multiple scopes as a space-separated string, e.g.
    `account:information order:execution market:information`.


    ## Security Best Practices


    - **Store credentials securely** – Use environment variables or a secrets
    manager for `client_id` and `client_secret`. Never hardcode them.

    - **Handle token expiration** – Check for 401 responses and refresh the
    token using the refresh_token, then retry the request.

    - **Use HTTPS** – All authorization and token endpoints must be called over
    HTTPS.

    - **Validate state** – When using the authorization code flow, validate the
    `state` parameter on the callback to prevent CSRF.


    ## Error Handling


    - **400 Bad Request** – Invalid or missing parameters, validation failures,
    or malformed JSON. Response bodies follow the same patterns as other errors
    (flat `error` string, optional `codes`, nested `error` object, or rarely no
    body).


    - **401 Unauthorized** – Invalid or expired access token; refresh the token
    or re-authenticate. JSON bodies are not identical on every route: you may
    see a flat `error` string (sometimes with `codes`), a nested `error` object
    (`type`, `code`, `message`), or rarely an empty body


    - **403 Forbidden** – Insufficient scope or permissions for the requested
    resource. Error JSON may be flat or nested, like 400/401.


    - **404 Not Found** – Resource does not exist or is not visible. Error JSON
    may be flat or nested.


    - **429 Too Many Requests** – Rate limit exceeded; slow down and respect
    `Retry-After` when the header is present. Error JSON may be flat or nested.


    - **500 / 5xx** – Server or upstream failure; retry with backoff. Do not
    depend on a single error JSON shape; some responses may have no body.



    ---


    Endpoints in this spec: health, OAuth2 (authorize, confirm, mfa, token),
    users, accounts, orders, market data, watchlist, chart, analytics,
    calendars, company, economy, financials, indices, options, news, and
    supplemental data.
  title: Aries API — OpenAPI Specification
  version: 1.0.0
servers:
  - description: Production server
    url: https://api.aries.com
security: []
tags:
  - description: >-
      Analytics endpoints for market data analysis including top gainers,
      losers, volume leaders, sector analysis, analyst ratings, market breadth,
      and net inflow
    name: Analytics
  - description: User management and profile endpoints
    name: Users
  - description: >-
      Order management endpoints for placing, updating, canceling, and
      previewing orders
    name: Orders
  - description: Account management endpoints for positions, orders, and balances
    name: Accounts
  - description: Calendar and mergers/acquisitions endpoints
    name: Calendar
  - description: >-
      Market data endpoints for symbol search, real-time data access, and equity
      details
    name: Market Data
  - description: >-
      Watchlist endpoints for listing, creating, updating, and deleting
      watchlists
    name: Watchlist
  - description: Chart endpoints for config, symbols, history, quotes, and server time
    name: Chart
  - description: News and news sentiment endpoints
    name: News
  - description: >-
      Indices endpoints for groups, list, search, bar, bars, chart-bars,
      realtime values
    name: Indices
  - description: Logos search and sync endpoints
    name: Logos
  - description: 'Corporate actions: spinoffs, tender offers, IPO calendar, dividends'
    name: Corporate Actions
  - description: 'Economy endpoints: inflation, inflation expectations, treasury yields'
    name: Economy
  - description: >-
      Options endpoints: expiry dates, contracts, activity, trades, quotes,
      unusual activity
    name: Options
  - description: >-
      Financials: reported, statements, revenue breakdown, short volume, ratios,
      short interest
    name: Financials
  - description: Signals and bull-bear cases
    name: Signals
  - name: Company
  - name: ETF
  - name: Filings
  - name: Market
  - name: Ownership
  - name: Stocks
  - name: Stock Estimates
  - name: Stock Alternative
    description: >-
      Transcripts, company presentation, social sentiment, investment themes,
      supply chain, and ESG data
  - name: Technical Analysis
paths:
  /v1/accounts/{id}/positions:
    get:
      tags:
        - Accounts
      summary: Get Account Positions
      description: >-
        Retrieve all open positions for a specific account. Use this for
        portfolio screens and pre-trade checks before selling or closing a
        position.
      operationId: getAccountPositions
      parameters:
        - description: >-
            Account whose positions you want to read. Enter the account ID from
            [User Accounts](/api-reference/accounts/user-accounts).
          in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GetPositionsResponse'
              example:
                positions:
                  - accountId: TEST-ACCOUNT-001
                    symbol: AAPL
                    securityType: EQUITY
                    quantity: '100'
                    averagePrice: '170.25'
                    price: '175.40'
                    todayRealizedPnL: '0'
                    unrealizedPl: '515.00'
                    todayPl: '125.00'
                    marketValue: '17540.00'
                    costBasis: '17025.00'
                    createdAt: '2026-01-10T16:00:00Z'
          description: Account positions retrieved successfully
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
        - OAuth2:
            - account:information
components:
  schemas:
    GetPositionsResponse:
      type: object
      description: >-
        Response for GET /v1/accounts/{id}/positions. Cached positions for the
        account. Numeric amounts are returned as decimal strings.
      properties:
        positions:
          type: array
          description: Open positions for the account
          items:
            $ref: '#/components/schemas/AccountPosition'
      example:
        positions:
          - accountId: TEST-ACCOUNT-001
            symbol: AAPL
            securityType: EQUITY
            quantity: '100'
            averagePrice: '170.25'
            todayRealizedPnL: '0'
            unrealizedPl: '515.00'
            todayPl: '125.00'
            createdAt: '2026-01-10T16:00:00Z'
    AccountPosition:
      type: object
      description: >-
        One open position in the account positions list. Field names and types
        are stable across REST and WebSocket order updates.
      properties:
        accountId:
          type: string
          description: >-
            Account this position belongs to. Match it to the account selected
            by the user.
        symbol:
          type: string
          description: >-
            Symbol for the held instrument. Use this to link the position to
            quotes, charts, and research.
        securityType:
          type: string
          description: >-
            Indicates whether the position is an equity/stock position or an
            option position. Use this value to decide which position details and
            actions should be shown in the UI.

            - `EQUITY` — a stock or ETF position.

            - `OPTION` — an option contract position.
          enum:
            - EQUITY
            - OPTION
        quantity:
          type: string
          description: >-
            Current position quantity, returned as a decimal string. Positive
            values represent long positions. Negative values may represent short
            positions, depending on brokerage conventions.
        averagePrice:
          type: string
          description: >-
            Average entry price per share or contract, returned as a decimal
            string. This value can be used with quantity and current market
            price to calculate position-level profit/loss.
        price:
          type: string
          description: >-
            Current market price per share or contract, returned as a decimal
            string. Omitted when unavailable (for example, on a closed position
            with zero quantity).
        todayRealizedPnL:
          type: string
          description: >-
            Profit or loss realized today from closed activity in this symbol,
            returned as a decimal string.
        unrealizedPl:
          type: string
          description: >-
            Profit or loss on the open position at current market prices,
            returned as a decimal string.
        todayPl:
          type: string
          description: >-
            Profit or loss for the current trading day, returned as a decimal
            string.
        marketValue:
          type: string
          description: >-
            Current market value of the position (`quantity` × `price`),
            returned as a decimal string. Omitted when unavailable (for example,
            on a closed position with zero quantity).
        costBasis:
          type: string
          description: >-
            Total cost basis of the position, returned as a decimal string.
            Omitted when unavailable (for example, on a closed position with
            zero quantity).
        createdAt:
          type: string
          format: date-time
          description: >-
            Timestamp when the current net position was established, returned in
            RFC3339 format. If the position is closed and reopened, this resets
            to the new establishment time rather than the first lot's open time.
            Omitted when not available.
    AuthenticationErrorEnvelope:
      type: object
      description: >-
        JSON envelope where `error` is a nested object (`type`, `code`,
        `message`, …). Many services use this shape for 400, 401, 403, 404, 429,
        and 5xx when using the shared HTTP error format.
      properties:
        error:
          $ref: '#/components/schemas/AuthenticationErrorDetail'
        meta:
          type: object
          additionalProperties: true
          description: Optional metadata
    ErrorResponse:
      properties:
        codes:
          description: Structured error codes for programmatic handling
          items:
            $ref: '#/components/schemas/ErrorCode'
          type: array
        error:
          type: string
          description: >-
            Error detail as a string. Exact message content is not fixed and
            should not be hard-coded.
          example: string
        metadata:
          additionalProperties: true
          description: Additional error context
          type: object
      type: object
    AuthenticationErrorDetail:
      type: object
      description: >-
        Structured payload for the nested `error` object. Services using the
        shared Go error writer emit uppercase `type` values aligned with error
        categories (for example `VALIDATION`, `BAD_REQUEST`, `AUTHENTICATION`,
        `AUTHORIZATION`, `NOT_FOUND`, `RATE_LIMIT`, `INTERNAL`).
      properties:
        type:
          type: string
          description: >-
            Error category emitted by the shared error writer, such as
            VALIDATION, BAD_REQUEST, AUTHENTICATION, AUTHORIZATION, NOT_FOUND,
            RATE_LIMIT, or INTERNAL.
          example: AUTHENTICATION
        code:
          type: string
          description: Machine-readable code
          example: INVALID_TOKEN
        message:
          type: string
          description: >-
            Error detail as a string. Exact message content is not fixed and
            should not be hard-coded.
          example: string
        details:
          type: object
          additionalProperties: true
          description: Optional extra context
        request_id:
          type: string
          description: Request correlation id when provided
    ErrorCode:
      properties:
        code:
          description: Machine-readable error code
          example: INVALID_PASSWORD
          type: string
        description:
          description: Human-readable description of the error
          example: Password must be between 8 and 64 characters
          type: string
        field:
          description: Field name that caused the error
          example: password
          type: string
      type: object
  responses:
    BadRequest:
      description: >-
        Invalid query/path/body, malformed JSON, or failed validation.


        **Backend (shared HTTP errors):** Routes that use the shared error
        writer typically return a **nested** JSON object: `error.type` is an
        uppercase category such as `VALIDATION` or `BAD_REQUEST` (see the API
        implementation). Other routes may return the **flat** `ErrorResponse`
        shape (`error` as a string, optional `codes`). The body may rarely be
        empty.


        **OpenAPI:** Every operation’s **400** response references this
        component so the documented schema and examples stay aligned.
      content:
        application/json:
          schema:
            oneOf:
              - $ref: '#/components/schemas/AuthenticationErrorEnvelope'
              - $ref: '#/components/schemas/ErrorResponse'
            description: >-
              400 responses commonly use a nested `error` object (`type`,
              `code`, `message`, optional `details`). Some services still return
              flat `ErrorResponse` (`error` string, optional `codes`).
          examples:
            nested_shared_http_error:
              summary: Nested error (typical for shared WriteHTTPError-style responses)
              value:
                error:
                  type: VALIDATION
                  code: VALIDATION_ERROR
                  message: Query parameter validation failed
                  details:
                    fieldName: field is required
            nested_bad_request:
              summary: Nested error (BAD_REQUEST / invalid JSON)
              value:
                error:
                  type: BAD_REQUEST
                  code: INVALID_JSON
                  message: string
            flat_string:
              summary: Flat error string
              value:
                error: string
            flat_with_codes:
              summary: Flat error with field codes (some services)
              value:
                error: string
                codes:
                  - field: string
                    code: string
                    description: string
    Unauthorized:
      description: >-
        Authentication failed: missing credentials, invalid JWT, or expired
        access token.


        **Response body variants:** Some routes return JSON with a string
        `error` field (and optionally `codes` / `metadata`). Others return JSON
        where `error` is a structured object (`type`, `code`, `message`, and
        optionally `details`, `request_id`). In edge cases (for example certain
        gateway or middleware paths) the response may have **no body** even
        though the status is 401—clients should treat 401 as unauthenticated and
        refresh or re-authenticate regardless of body shape.
      content:
        application/json:
          schema:
            oneOf:
              - $ref: '#/components/schemas/ErrorResponse'
              - $ref: '#/components/schemas/AuthenticationErrorEnvelope'
            description: >-
              401 responses may use a flat `ErrorResponse` shape or a nested
              `error` object. Inspect `error`: if it is a string, use the flat
              shape; if it is an object, use the structured shape.
          examples:
            flat_message:
              summary: Flat error string (typical)
              value:
                error: string
            flat_with_codes:
              summary: Flat error with structured codes
              value:
                error: string
                codes:
                  - code: string
                    description: string
            nested_error_object:
              summary: Nested error (shared HTTP error format)
              value:
                error:
                  type: AUTHENTICATION
                  code: INVALID_TOKEN
                  message: string
    Forbidden:
      description: >-
        Authenticated but not allowed to perform this action or access this
        resource (insufficient scope or role).


        **Response body variants:** Flat `ErrorResponse` or nested `error`
        object, same pattern as 400/401.
      content:
        application/json:
          schema:
            oneOf:
              - $ref: '#/components/schemas/ErrorResponse'
              - $ref: '#/components/schemas/AuthenticationErrorEnvelope'
            description: 403 responses may use a flat or nested error shape.
          examples:
            flat:
              summary: Flat message
              value:
                error: string
            nested:
              summary: Nested error (shared HTTP error format)
              value:
                error:
                  type: AUTHORIZATION
                  code: INSUFFICIENT_SCOPE
                  message: string
    NotFound:
      description: >-
        The requested resource does not exist or is not visible to this caller.


        **Response body variants:** Flat `ErrorResponse` or nested `error`
        object.
      content:
        application/json:
          schema:
            oneOf:
              - $ref: '#/components/schemas/ErrorResponse'
              - $ref: '#/components/schemas/AuthenticationErrorEnvelope'
            description: 404 responses may use a flat or nested error shape.
          examples:
            flat:
              summary: Flat message
              value:
                error: string
            nested:
              summary: Nested error (shared HTTP error format)
              value:
                error:
                  type: NOT_FOUND
                  code: RESOURCE_NOT_FOUND
                  message: string
    RateLimitExceeded:
      description: >-
        Too many requests for this client or IP. **Slow down** and retry after a
        delay.


        **Headers:** When present, `Retry-After` indicates how long to wait
        (seconds or an HTTP-date).


        **Body:** Flat `ErrorResponse` or nested `error` object, same as other
        errors.
      headers:
        Retry-After:
          description: >-
            Seconds to wait before retrying, or an HTTP-date (RFC 7231). Omitted
            if not applicable.
          schema:
            type: string
            example: '60'
      content:
        application/json:
          schema:
            oneOf:
              - $ref: '#/components/schemas/ErrorResponse'
              - $ref: '#/components/schemas/AuthenticationErrorEnvelope'
            description: 429 responses may use a flat or nested error shape.
          examples:
            flat:
              summary: Flat rate-limit message
              value:
                error: string
            nested:
              summary: Nested error (shared HTTP error format)
              value:
                error:
                  type: RATE_LIMIT
                  code: TOO_MANY_REQUESTS
                  message: string
    InternalServerError:
      description: >-
        Unexpected server error or upstream failure. **Retry with exponential
        backoff**; do not assume a specific JSON body shape.


        **Response body variants:** Flat `ErrorResponse`, nested `error` object,
        or occasionally a minimal message. Some paths may return **no body**.
      content:
        application/json:
          examples:
            flat_string:
              summary: Flat error string
              value:
                error: string
            nested_error_object:
              summary: Nested error (shared HTTP error format)
              value:
                error:
                  type: INTERNAL
                  code: INTERNAL_ERROR
                  message: string
          schema:
            oneOf:
              - $ref: '#/components/schemas/ErrorResponse'
              - $ref: '#/components/schemas/AuthenticationErrorEnvelope'
            description: 5xx responses may use a flat or nested error shape.
  securitySchemes:
    OAuth2:
      type: oauth2
      description: >-
        OAuth2 Bearer token: obtain an access token from the token endpoint and
        send it in the Authorization header.
      flows:
        clientCredentials:
          tokenUrl: https://api.aries.com/v1/oauth2/token
          refreshUrl: https://api.aries.com/v1/oauth2/token
          scopes:
            user:information: View user profile and personal details
            account:information: View account balances, positions, and transaction history
            order:execution: Place, modify, and cancel orders
            order:information: View order history and status
            position:information: View current positions and holdings
            market:information: Access live and historical market data
            calendar:information: Access earnings, economic, and market schedule data
            options:information: Access options chains and expiration data
            analytics:information: View analytics, ratings, and market insights
            market:supplemental: >-
              News, company profiles, financials, filings, ETF data, technical
              analysis
            user:management: >-
              Manage user-scoped resources such as watchlists and other saved
              configuration.
        authorizationCode:
          authorizationUrl: https://app.aries.com/oauth2/authorize
          tokenUrl: https://api.aries.com/v1/oauth2/token
          refreshUrl: https://api.aries.com/v1/oauth2/token
          scopes:
            user:information: View user profile and personal details
            account:information: View account balances, positions, and transaction history
            order:execution: Place, modify, and cancel orders
            order:information: View order history and status
            position:information: View current positions and holdings
            market:information: Access live and historical market data
            calendar:information: Access earnings, economic, and market schedule data
            options:information: Access options chains and expiration data
            analytics:information: View analytics, ratings, and market insights
            market:supplemental: >-
              News, company profiles, financials, filings, ETF data, technical
              analysis
            user:management: >-
              Manage user-scoped resources such as watchlists and other saved
              configuration.

````