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

# Easy-to-Borrow List

> Returns every symbol on today's easy-to-borrow (ETB) list — the complete set of US equities that can be sold short without first locating shares. Membership is the whole signal: there is no per-symbol status, quantity, or borrow rate.

**Use Case:** Build a screener or filter a large watchlist by fetching the list once per trading day and checking membership locally.


export const OAuthLoginButton = ({scopes = "user:information watchlist:information watchlist:management 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, requiredScopes = []}) => {
  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}

      {requiredScopes.length > 0 && <Info>
          <strong>
            {requiredScopes.length === 1 && requiredScopes[0].length === 1 ? "Required scope" : requiredScopes.length === 1 ? "Required scopes" : "Required scope (any one of)"}
          </strong>{" "}
          {requiredScopes.map((alternative, i) => <span key={i}>
              {i > 0 && " or "}
              {alternative.map((scope, j) => <span key={scope}>
                  {j > 0 && " + "}
                  <code>{scope}</code>
                </span>)}
            </span>)}
        </Info>}
    </>;
};

<OAuthLoginButton requiredScopes={[["market:information"]]} />

## Why borrowability matters

Some US equities cannot be sold short without first borrowing shares. Aries receives a daily list of the symbols that *are* freely borrowable from its clearing broker, and this endpoint exposes it.

The list answers one question: **is this symbol freely short-sellable today?**

| Term | Meaning |
| - | - |
| **Easy to borrow (ETB)** | The symbol is on today's list. A short sale needs no extra step. |
| **Hard to borrow (HTB)** | The symbol is **not** on today's list. Shares must be located before a short sale, and a locate usually carries a fee. |

Borrowability is a property of the symbol, not of an account — this endpoint takes no account ID and every authenticated caller gets the same answer.

## Absence is an answer, not missing data

The list is the **complete** borrowable universe for the trading day. A symbol not being on it is a positive statement that it is hard to borrow — not "unknown". Integrators frequently assume the opposite, so treat a missing symbol as a definite HTB result.

## Response fields

| Field | Type | Notes |
| - | - | - |
| `symbols` | `string[]` | Uppercase, sorted ascending, no duplicates. Always an array — never `null` |
| `count` | `integer` | Length of `symbols` |

```json theme={null}
{
  "symbols": ["A", "AA", "AAPL", "ABBRF", "ACN"],
  "count": 5
}
```

The order is imposed by the service, so two calls on the same day return the same order. Symbols carrying a leading `.` (for example `.VFV`) are non-US listings and are returned as-is.

<Warning>
  **An empty list is a real `200`, not an error.** Before the day's list has loaded, this endpoint returns `{"symbols": [], "count": 0}`. That means *the list is not loaded yet* — it does **not** mean everything is borrowable, and it does not mean nothing is borrowable. Handle `count: 0` as "unknown" and retry rather than acting on it.
</Warning>

## Response size

The borrowable universe runs to thousands of symbols, so the response can be a few hundred KB. If you only need to check a handful of symbols, use [Easy-to-Borrow Search](/api-reference/marketdata/easy-to-borrow-search) instead of filtering this payload yourself.

## Freshness

* The list is **replaced once per trading day**, before market open, and does not change intraday.
* **A failed daily update leaves the previous day's list in place.** The list is never emptied on failure, so a stale list is possible but a wrongly-empty one is not.
* **No caching headers are set.** If you cache, cache for the trading day and refresh after the next market open.

## Choosing between the two endpoints

<CardGroup cols={2}>
  <Card title="Fetch the whole list" icon="list">
    Use `GET /v1/etb` when you are building a screener or filtering a large watchlist. Fetch once per trading day and check membership locally.
  </Card>

  <Card title="Check specific symbols" icon="magnifying-glass" href="/api-reference/marketdata/easy-to-borrow-search">
    Use `POST /v1/etb/search` when you only care about a handful of symbols — for example, whether to show a short-sell option on a ticker page.
  </Card>
</CardGroup>

Both endpoints read the same underlying set, so they can never disagree with each other.

<Note>
  When a symbol comes back as hard to borrow, treat it as "this needs a locate first", not "this can never be shorted". Requesting a locate is not currently supported through the API.
</Note>


## OpenAPI

````yaml openapi.json GET /v1/etb
openapi: 3.0.3
info:
  contact:
    email: support@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 |

    | `watchlist:information` | View your watchlists and their symbols |

    | `watchlist:management` | Create, update, replace, and delete your
    watchlists |

    | `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.


    - **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
  - description: Staging server
    url: https://api.tradearies.dev
security: []
tags:
  - description: Account management endpoints for positions, orders, and balances
    name: Accounts
  - 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: Calendar and mergers/acquisitions endpoints
    name: Calendar
  - description: Chart endpoints for config, symbols, history, quotes, and server time
    name: Chart
  - description: Company profile, executives, peers, and financial metrics
    name: Company
  - description: 'Corporate actions: spinoffs, tender offers, IPO calendar, dividends'
    name: Corporate Actions
  - description: ETF profile, holdings, sector allocation, and country allocation
    name: ETF
  - description: 'Economy endpoints: inflation, inflation expectations, treasury yields'
    name: Economy
  - description: SEC filings and filing similarity index
    name: Filings
  - description: >-
      Financials: reported, statements, revenue breakdown, short volume, ratios,
      short interest
    name: Financials
  - description: >-
      Indices endpoints for groups, list, search, bar, bars, chart-bars,
      realtime values
    name: Indices
  - description: Logos search and sync endpoints
    name: Logos
  - description: Market status and market holidays by exchange
    name: Market
  - description: >-
      Market data endpoints for symbol search, real-time data access, and equity
      details
    name: Market Data
  - description: News and news sentiment endpoints
    name: News
  - description: >-
      OAuth2 token issuance: exchange authorization codes, refresh tokens, or
      client credentials for access tokens
    name: OAuth2
  - description: >-
      Options endpoints: expiry dates, contracts, activity, trades, quotes,
      unusual activity
    name: Options
  - description: >-
      Order management endpoints for placing, updating, canceling, and
      previewing orders
    name: Orders
  - description: Company, fund, insider, and institutional ownership data
    name: Ownership
  - description: Signals and bull-bear cases
    name: Signals
  - description: >-
      Transcripts, company presentation, social sentiment, investment themes,
      supply chain, and ESG data
    name: Stock Alternative
  - description: >-
      Analyst price targets, upgrades and downgrades, revenue, EBITDA, EBIT, and
      EPS estimates, and earnings surprises
    name: Stock Estimates
  - description: Stock aggregate bars, daily summaries, snapshots, trades, and quotes
    name: Stocks
  - description: >-
      Technical patterns, support and resistance levels, aggregate technical
      indicators, and indicator time series
    name: Technical Analysis
  - description: >-
      Watchlist endpoints for listing, creating, updating, and deleting
      watchlists
    name: Watchlist
  - description: Historical YES/NO tick data for prediction-market contracts
    name: Prediction Historical Data
  - description: >-
      Prediction-market reference data and full-text search over base events,
      events, and contracts
    name: Prediction Market Data
  - description: >-
      Closed beta. Place prediction-market orders and read back the
      authenticated user's order history.
    name: Prediction Orders
paths:
  /v1/etb:
    get:
      tags:
        - Market Data
      summary: Get Easy-to-Borrow List
      description: >-
        Returns every symbol on today's easy-to-borrow (ETB) list: the complete
        set of US equities that can be sold short without first locating shares.
        A symbol on the list is easy to borrow and can be shorted normally; a
        symbol absent from it is hard to borrow (HTB) and cannot be shorted
        without a locate. Absence is a positive answer, not missing data. The
        list is replaced once per trading day before the market opens and does
        not change intraday. It is not account-scoped — borrowability is a
        property of the symbol, so every caller gets the same answer. The
        response can reach a few hundred KB because the borrowable universe runs
        to thousands of symbols; fetch it once per trading day and check
        membership locally, or use POST /v1/etb/search when you only care about
        a handful of symbols.
      operationId: getEtbList
      responses:
        '200':
          content:
            application/json:
              examples:
                not_loaded:
                  summary: List not loaded yet (do not read as "nothing is borrowable")
                  value:
                    count: 0
                    symbols: []
                success:
                  summary: List loaded
                  value:
                    count: 5
                    symbols:
                      - A
                      - AA
                      - AAPL
                      - ABBRF
                      - ACN
              schema:
                $ref: '#/components/schemas/EtbListResponse'
          description: >-
            The borrowable universe currently in effect. An empty list is a
            valid 200: before the day's list has loaded this returns
            `{"symbols": [], "count": 0}` rather than an error, which means the
            list is not loaded yet — it must not be read as "nothing is
            borrowable". No caching headers are set; cache for the trading day
            and refresh after the next market open.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '500':
          $ref: '#/components/responses/InternalServerError'
      security:
        - OAuth2:
            - market:information
      x-codeSamples:
        - lang: python
          label: Python (SDK)
          source: |-
            from finance_dev import FinanceDev, models
            import os


            with FinanceDev(
                security=models.Security(
                    client_id=os.getenv("FINANCE_DEV_CLIENT_ID", ""),
                    client_secret=os.getenv("FINANCE_DEV_CLIENT_SECRET", ""),
                ),
            ) as fd_client:

                res = fd_client.market.get_etb_list()

                assert res.etb_list_response is not None

                # Handle response
                print(res.etb_list_response)
components:
  schemas:
    EtbListResponse:
      description: >-
        The complete set of easy-to-borrow symbols in effect for the current
        trading day. Membership in the list is the whole signal: a symbol on the
        list can be sold short normally, and a symbol absent from it is hard to
        borrow. There is no per-symbol status field, quantity, or borrow rate.
      properties:
        count:
          description: Number of entries in `symbols`.
          type: integer
        symbols:
          description: >-
            Every borrowable symbol currently in effect — uppercase, sorted
            ascending, and free of duplicates. The order is imposed by the
            service, so two calls on the same day return the same order. Always
            an array, never null. An empty array means the day's list has not
            loaded yet, NOT that nothing is borrowable.
          items:
            example: AAPL
            type: string
          type: array
      type: object
    ErrorResponse:
      description: >-
        Flat error body: an `error` string, with optional structured `codes` and
        additional `metadata`.
      properties:
        codes:
          description: Structured error codes for programmatic handling
          items:
            $ref: '#/components/schemas/ErrorCode'
          type: array
        error:
          description: >-
            Error detail as a string. Exact message content is not fixed and
            should not be hard-coded.
          example: string
          type: string
        metadata:
          additionalProperties: true
          description: Additional error context
          type: object
      type: object
    AuthenticationErrorEnvelope:
      description: >-
        JSON envelope where `error` is a nested object (`type`, `code`,
        `message`, …). Many services use this shape for 400, 401, 403, 404, and
        5xx when using the shared HTTP error format.
      properties:
        error:
          $ref: '#/components/schemas/AuthenticationErrorDetail'
        meta:
          additionalProperties: true
          description: Optional metadata
          type: object
      type: object
    ErrorCode:
      description: >-
        A single structured error entry identifying a machine-readable code, a
        human-readable description, and optionally the field that caused it.
      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
    AuthenticationErrorDetail:
      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:
        code:
          description: Machine-readable code
          example: INVALID_TOKEN
          type: string
        details:
          additionalProperties: true
          description: Optional extra context
          type: object
        message:
          description: >-
            Error detail as a string. Exact message content is not fixed and
            should not be hard-coded.
          example: string
          type: string
        request_id:
          description: Request correlation id when provided
          type: string
        type:
          description: >-
            Error category emitted by the shared error writer, such as
            VALIDATION, BAD_REQUEST, AUTHENTICATION, AUTHORIZATION, NOT_FOUND,
            RATE_LIMIT, or INTERNAL.
          example: AUTHENTICATION
          type: string
      type: object
  responses:
    Unauthorized:
      content:
        application/json:
          examples:
            flat_message:
              summary: Flat error string (typical)
              value:
                error: string
            flat_with_codes:
              summary: Flat error with structured codes
              value:
                codes:
                  - code: string
                    description: string
                error: string
            nested_error_object:
              summary: Nested error (shared HTTP error format)
              value:
                error:
                  code: INVALID_TOKEN
                  message: string
                  type: AUTHENTICATION
          schema:
            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.
            oneOf:
              - $ref: '#/components/schemas/ErrorResponse'
              - $ref: '#/components/schemas/AuthenticationErrorEnvelope'
      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.
    Forbidden:
      content:
        application/json:
          examples:
            flat:
              summary: Flat message
              value:
                error: string
            nested:
              summary: Nested error (shared HTTP error format)
              value:
                error:
                  code: INSUFFICIENT_SCOPE
                  message: string
                  type: AUTHORIZATION
          schema:
            description: 403 responses may use a flat or nested error shape.
            oneOf:
              - $ref: '#/components/schemas/ErrorResponse'
              - $ref: '#/components/schemas/AuthenticationErrorEnvelope'
      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.
    InternalServerError:
      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:
                  code: INTERNAL_ERROR
                  message: string
                  type: INTERNAL
          schema:
            description: 5xx responses may use a flat or nested error shape.
            oneOf:
              - $ref: '#/components/schemas/ErrorResponse'
              - $ref: '#/components/schemas/AuthenticationErrorEnvelope'
      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.
  securitySchemes:
    OAuth2:
      description: >-
        OAuth2 Bearer token: obtain an access token from the token endpoint and
        send it in the Authorization header.
      flows:
        authorizationCode:
          authorizationUrl: https://app.aries.com/oauth2/authorize
          refreshUrl: https://api.aries.com/v1/oauth2/token
          scopes:
            account:information: View account balances, positions, and transaction history
            analytics:information: View analytics, ratings, and market insights
            calendar:information: Access earnings, economic, and market schedule data
            market:information: Access live and historical market data
            market:supplemental: >-
              News, company profiles, financials, filings, ETF data, technical
              analysis
            options:information: Access options chains and expiration data
            order:execution: Place, modify, and cancel orders
            order:information: View order history and status
            position:information: View current positions and holdings
            user:information: View user profile and personal details
            user:management: Manage user-scoped resources and saved configuration (internal).
            watchlist:information: View your watchlists and their symbols.
            watchlist:management: Create, update, replace, and delete your watchlists.
          tokenUrl: https://api.aries.com/v1/oauth2/token
        clientCredentials:
          refreshUrl: https://api.aries.com/v1/oauth2/token
          scopes:
            account:information: View account balances, positions, and transaction history
            analytics:information: View analytics, ratings, and market insights
            calendar:information: Access earnings, economic, and market schedule data
            market:information: Access live and historical market data
            market:supplemental: >-
              News, company profiles, financials, filings, ETF data, technical
              analysis
            options:information: Access options chains and expiration data
            order:execution: Place, modify, and cancel orders
            order:information: View order history and status
            position:information: View current positions and holdings
            user:information: View user profile and personal details
            user:management: Manage user-scoped resources and saved configuration (internal).
            watchlist:information: View your watchlists and their symbols.
            watchlist:management: Create, update, replace, and delete your watchlists.
          tokenUrl: https://api.aries.com/v1/oauth2/token
      type: oauth2

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.