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

# API Query Builder

> Convert natural language into OpenSearch DSL and can run it against the Swarm API, delivering both the query and live results in one interface.

export const QueryBuilder = () => {
  const [query, setQuery] = useState("");
  const [result, setResult] = useState("");
  const [convertedQuery, setConvertedQuery] = useState("");
  const [loading, setLoading] = useState(false);
  const [queryType, setQueryType] = useState("profiles");
  const [copiedResult, setCopiedResult] = useState(false);
  const [copiedQuery, setCopiedQuery] = useState(false);
  const [apiKey, setApiKey] = useState("");
  const [showApiKey, setShowApiKey] = useState(false);
  const [maxResults, setMaxResults] = useState(20);
  const [lastSeen, setLastSeen] = useState(null);
  const profilePlaceholder = "Find CTOs in fintech startups in London";
  const companyPlaceholder = "Companies that raised Series A funding in 2024";
  const currentPlaceholder = queryType === "profiles" ? profilePlaceholder : companyPlaceholder;
  useEffect(() => {
    const savedApiKey = localStorage.getItem("swarm-api-key");
    if (savedApiKey) {
      setApiKey(savedApiKey);
    }
  }, []);
  const handleApiKeyChange = e => {
    const newApiKey = e.target.value;
    setApiKey(newApiKey);
    if (newApiKey.trim()) {
      localStorage.setItem("swarm-api-key", newApiKey);
    } else {
      localStorage.removeItem("swarm-api-key");
    }
  };
  const clearApiKey = () => {
    setApiKey("");
    localStorage.removeItem("swarm-api-key");
  };
  const handleSubmit = async () => {
    if (!query.trim()) return;
    setLoading(true);
    setResult("");
    setConvertedQuery("");
    try {
      const convertResponse = await fetch("https://theswarm-query-builder.vercel.app/api/convert", {
        method: "POST",
        headers: {
          "Content-Type": "application/json"
        },
        body: JSON.stringify({
          query: query.trim(),
          type: queryType,
          model: "openai"
        })
      });
      const convertData = await convertResponse.json();
      if (!convertData.success) {
        setResult(`Error: ${convertData.error}`);
        setLoading(false);
        return;
      }
      const dslQuery = convertData.query;
      setConvertedQuery(JSON.stringify(dslQuery, null, 2));
      if (apiKey.trim()) {
        try {
          const executeResponse = await fetch("https://theswarm-query-builder.vercel.app/api/execute", {
            method: "POST",
            headers: {
              "Content-Type": "application/json"
            },
            body: JSON.stringify({
              query: dslQuery,
              type: queryType,
              apiKey: apiKey.trim(),
              max: maxResults,
              after: lastSeen
            })
          });
          const executeData = await executeResponse.json();
          if (executeData.success) {
            setResult(JSON.stringify(executeData.data, null, 2));
            if (executeData.data?.lastSeen) {
              setLastSeen(executeData.data.lastSeen);
            }
          } else {
            setResult(`Error: ${executeData.error}`);
          }
        } catch (executeError) {
          setResult("Failed to execute query");
        }
      } else {
        setResult(JSON.stringify(dslQuery, null, 2));
      }
    } catch (error) {
      setResult("Failed to connect to server");
    } finally {
      setLoading(false);
    }
  };
  const handleKeyPress = e => {
    if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
      handleSubmit();
    }
  };
  const loadNextPage = () => {
    if (lastSeen && apiKey.trim()) {
      handleSubmit();
    }
  };
  const resetPagination = () => {
    setLastSeen(null);
    setResult("");
  };
  const copyToClipboard = async () => {
    if (!result) return;
    try {
      await navigator.clipboard.writeText(result);
      setCopiedResult(true);
      setTimeout(() => setCopiedResult(false), 1500);
    } catch (error) {
      console.error("Failed to copy to clipboard:", error);
    }
  };
  const copyConvertedQuery = async () => {
    if (!convertedQuery) return;
    try {
      await navigator.clipboard.writeText(convertedQuery);
      setCopiedQuery(true);
      setTimeout(() => setCopiedQuery(false), 1500);
    } catch (error) {
      console.error("Failed to copy to clipboard:", error);
    }
  };
  const downloadResults = () => {
    if (!result || result.startsWith("Error:")) return;
    try {
      const data = JSON.parse(result);
      const dataStr = JSON.stringify(data, null, 2);
      const dataBlob = new Blob([dataStr], {
        type: "application/json"
      });
      const url = URL.createObjectURL(dataBlob);
      const link = document.createElement("a");
      link.href = url;
      link.download = `swarm-query-results-${Date.now()}.json`;
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);
      URL.revokeObjectURL(url);
    } catch (error) {
      console.error("Failed to download results:", error);
    }
  };
  const handleQueryChange = e => {
    setQuery(e.target.value);
    if (lastSeen) {
      setLastSeen(null);
    }
  };
  const renderJSON = text => {
    if (!text) return null;
    if (text.startsWith("Error:")) {
      return <div className="json-error">{text}</div>;
    }
    try {
      const jsonObj = JSON.parse(text);
      const renderValue = (value, indent = 0) => {
        const spaces = ("  ").repeat(indent);
        if (value === null) {
          return <span className="json-null">null</span>;
        }
        if (typeof value === "boolean") {
          return <span className="json-boolean">{String(value)}</span>;
        }
        if (typeof value === "number") {
          return <span className="json-number">{value}</span>;
        }
        if (typeof value === "string") {
          return <span className="json-string">"{value}"</span>;
        }
        if (Array.isArray(value)) {
          if (value.length === 0) {
            return <>
                <span className="json-punctuation">[</span>
                <span className="json-punctuation">]</span>
              </>;
          }
          return <>
              <span className="json-punctuation">[</span>
              <div style={{
            marginLeft: "20px"
          }}>
                {value.map((item, index) => <div key={index}>
                    {spaces}
                    {renderValue(item, indent + 1)}
                    {index < value.length - 1 && <span className="json-punctuation">,</span>}
                  </div>)}
              </div>
              {spaces}<span className="json-punctuation">]</span>
            </>;
        }
        if (typeof value === "object") {
          const keys = Object.keys(value);
          if (keys.length === 0) {
            return <>
                <span className="json-punctuation">{`{`}</span>
                <span className="json-punctuation">{`}`}</span>
              </>;
          }
          return <>
              <span className="json-punctuation">{`{`}</span>
              <div style={{
            marginLeft: "20px"
          }}>
                {keys.map((key, index) => <div key={key}>
                    {spaces}
                    <span className="json-key">"{key}"</span>
                    <span className="json-punctuation">: </span>
                    {renderValue(value[key], indent + 1)}
                    {index < keys.length - 1 && <span className="json-punctuation">,</span>}
                  </div>)}
              </div>
              {spaces}<span className="json-punctuation">{`}`}</span>
            </>;
        }
        return String(value);
      };
      return renderValue(jsonObj);
    } catch (e) {
      return <div>{text}</div>;
    }
  };
  return <div style={{
    padding: "12px 24px 24px",
    border: "1px solid rgba(255, 255, 255, 0.1)",
    borderRadius: "12px",
    margin: "20px 0",
    background: "rgba(255, 255, 255, 0.05)",
    color: "#f9fafb",
    fontFamily: "Roobert Pro, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif"
  }}>

      {}
      <div style={{
    marginBottom: "24px"
  }}>
        <div style={{
    display: "flex",
    background: "transparent",
    borderRadius: "0",
    padding: "0",
    gap: "0"
  }}>
          <button onClick={() => setQueryType("profiles")} style={{
    flex: "none",
    padding: "12px 20px",
    border: "none",
    borderRadius: "0",
    background: "transparent",
    color: queryType === "profiles" ? "rgb(255, 157, 87)" : "rgba(255,255,255,0.9)",
    cursor: "pointer",
    fontSize: "14px",
    fontWeight: "500",
    borderBottom: queryType === "profiles" ? "1px solid rgb(255, 157, 87)" : "none",
    transition: "all 0.2s ease"
  }}>
            Profiles
          </button>
          <button onClick={() => setQueryType("companies")} style={{
    flex: "none",
    padding: "12px 20px",
    border: "none",
    borderRadius: "0",
    background: "transparent",
    color: queryType === "companies" ? "rgb(255, 157, 87)" : "rgba(255,255,255,0.9)",
    cursor: "pointer",
    fontSize: "14px",
    fontWeight: "500",
    borderBottom: queryType === "companies" ? "1px solid rgb(255, 157, 87)" : "none",
    transition: "all 0.2s ease"
  }}>
            Companies
          </button>
        </div>
      </div>

      {}
      <div style={{
    marginBottom: "8px",
    position: "relative"
  }}>
        <div style={{
    fontSize: "14px",
    fontWeight: "500",
    color: "rgba(255,255,255,0.8)",
    marginBottom: "8px"
  }}>
          Type your natural language query:
        </div>
        <textarea value={query} onChange={handleQueryChange} onKeyDown={handleKeyPress} placeholder="" autoComplete="off" data-1p-ignore data-lpignore="true" data-form-type="other" style={{
    width: "100%",
    padding: "12px 16px",
    border: "1px solid rgba(255,255,255,0.2)",
    borderRadius: "8px",
    background: "rgba(0,0,0,0.5)",
    color: "#f9fafb",
    fontSize: "14px",
    lineHeight: "1.5",
    resize: "vertical",
    minHeight: "80px",
    fontFamily: "inherit"
  }} />
        {}
        {!query && <div style={{
    position: "absolute",
    top: "44px",
    left: "16px",
    right: "16px",
    pointerEvents: "none",
    color: "rgba(255,255,255,0.5)",
    fontSize: "14px",
    lineHeight: "1.5",
    fontFamily: "inherit",
    whiteSpace: "pre-wrap",
    wordWrap: "break-word"
  }}>
            {currentPlaceholder}
          </div>}
      </div>

      {}
      <div style={{
    marginBottom: "20px"
  }}>
        <div style={{
    fontSize: "14px",
    fontWeight: "500",
    color: "rgba(255,255,255,0.8)",
    marginBottom: "8px"
  }}>
          Or, try these examples:
        </div>
        <div style={{
    display: "flex",
    flexWrap: "wrap",
    gap: "8px"
  }}>
          {(queryType === "profiles" ? ["Find CEOs at AI companies", "CTOs in fintech startups", "Data scientists in San Francisco"] : ["Companies that raised Series A", "AI startups in California", "Fintech companies over $10M"]).map((example, index) => <button key={index} onClick={() => setQuery(example)} style={{
    padding: "4px 8px",
    background: "rgba(255,255,255,0.1)",
    borderRadius: "8px",
    color: "#d1d5db",
    fontSize: "12px",
    cursor: "pointer",
    transition: "all 0.2s"
  }} onMouseOver={e => {
    e.target.style.background = "rgba(255,255,255,0.2)";
    e.target.style.color = "rgba(255,157,87,1)";
  }} onMouseOut={e => {
    e.target.style.background = "rgba(255,255,255,0.1)";
    e.target.style.color = "#d1d5db";
  }}>
              {example}
            </button>)}
        </div>
      </div>

      {}
      <div style={{
    marginBottom: "24px"
  }}>
        <div style={{
    marginBottom: "8px",
    fontSize: "14px",
    fontWeight: "500",
    color: "rgba(255,255,255,0.9)"
  }}>
          Swarm API Key (generate the key from <a href="https://app.theswarm.com/team-settings/api" target="_blank" rel="noopener noreferrer" style={{
    color: "inherit",
    textDecoration: "underline"
  }}>Team Settings</a>)
        </div>
        <div style={{
    position: "relative"
  }}>
          <input type={showApiKey ? "text" : "password"} value={apiKey} onChange={handleApiKeyChange} placeholder="Enter your Swarm API key" autoComplete="off" data-1p-ignore data-lpignore="true" data-form-type="other" style={{
    width: "100%",
    padding: "12px 16px",
    border: "1px solid rgba(255,255,255,0.2)",
    borderRadius: "8px",
    background: "rgba(0,0,0,0.5)",
    color: "#f9fafb",
    fontSize: "14px",
    fontFamily: "inherit"
  }} />
          <div style={{
    position: "absolute",
    right: "12px",
    top: "50%",
    transform: "translateY(-50%)",
    display: "flex",
    gap: "8px"
  }}>
            {apiKey && <button onClick={clearApiKey} style={{
    background: "none",
    border: "none",
    color: "rgba(255,255,255,0.6)",
    cursor: "pointer",
    padding: "4px"
  }} title="Clear API key">
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
                </svg>
              </button>}
            <button onClick={() => setShowApiKey(!showApiKey)} style={{
    background: "none",
    border: "none",
    color: "rgba(255,255,255,0.6)",
    cursor: "pointer",
    padding: "4px"
  }} title={showApiKey ? "Hide API key" : "Show API key"}>
              {showApiKey ? <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.878 9.878L3 3m6.878 6.878L21 21" />
                </svg> : <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
                </svg>}
            </button>
          </div>
        </div>
        <div style={{
    fontSize: "12px",
    color: "rgba(255,255,255,0.6)",
    marginTop: "4px"
  }}>
          Your API key is stored locally and never sent to our servers
        </div>
      </div>

      {}
      {apiKey.trim() && <div style={{
    marginBottom: "16px"
  }}>
          <div style={{
    marginBottom: "8px",
    fontSize: "14px",
    fontWeight: "500",
    color: "rgba(255,255,255,0.9)"
  }}>
            Max Results
          </div>
          <input type="number" min="1" max="100" value={maxResults} onChange={e => setMaxResults(parseInt(e.target.value) || 20)} style={{
    width: "100%",
    padding: "12px 16px",
    border: "1px solid rgba(255,255,255,0.2)",
    borderRadius: "8px",
    background: "rgba(0,0,0,0.5)",
    color: "#f9fafb",
    fontSize: "14px",
    fontFamily: "inherit"
  }} />
        </div>}

      {}
      <button onClick={handleSubmit} disabled={loading || !query.trim()} style={{
    width: "100%",
    padding: "12px 24px",
    background: loading || !query.trim() ? "rgb(55 55 55)" : "rgb(255, 157, 87)",
    color: !loading && query.trim() ? "black" : "white",
    border: "none",
    borderRadius: "8px",
    fontSize: "15px",
    fontWeight: "600",
    cursor: loading || !query.trim() ? "not-allowed" : "pointer",
    marginBottom: "16px",
    display: "flex",
    alignItems: "center",
    justifyContent: "center",
    gap: "8px"
  }}>
        {loading ? <>
            <div style={{
    width: "16px",
    height: "16px",
    border: "2px solid transparent",
    borderTop: "2px solid white",
    borderRadius: "50%",
    animation: "spin 1s linear infinite"
  }} />
            {apiKey.trim() ? "Converting & Testing..." : "Converting..."}
          </> : <>
            {apiKey.trim() ? "Convert & Test" : "Convert"}
          </>}
      </button>

      {}
      {lastSeen && apiKey.trim() && <div style={{
    display: "flex",
    gap: "12px",
    marginBottom: "16px"
  }}>
          <button onClick={loadNextPage} disabled={loading} style={{
    flex: "1",
    padding: "8px 16px",
    background: "rgba(255,255,255,0.1)",
    border: "1px solid rgba(255,255,255,0.2)",
    borderRadius: "6px",
    color: "#f9fafb",
    fontSize: "14px",
    cursor: loading ? "not-allowed" : "pointer",
    opacity: loading ? 0.5 : 1
  }}>
            Load Next Page
          </button>
          <button onClick={resetPagination} style={{
    padding: "8px 16px",
    background: "rgba(255,255,255,0.1)",
    border: "1px solid rgba(255,255,255,0.2)",
    borderRadius: "6px",
    color: "#f9fafb",
    fontSize: "14px",
    cursor: "pointer"
  }}>
            Reset
          </button>
        </div>}

      {}
      {result && <div style={{
    marginTop: "20px"
  }}>
          <div style={{
    display: "flex",
    alignItems: "center",
    justifyContent: "space-between",
    marginBottom: "12px"
  }}>
            <h4 style={{
    margin: 0,
    fontSize: "16px",
    fontWeight: "600"
  }}>
              {result.startsWith("Error:") ? "Error" : apiKey.trim() ? "API Response" : "Generated Query"}
            </h4>
            {!result.startsWith("Error:") && <div style={{
    display: "flex",
    gap: "8px"
  }}>
                <button onClick={copyToClipboard} style={{
    padding: "6px 12px",
    background: copiedResult ? "rgba(255, 140, 0, 0.15)" : "rgba(255,255,255, 0.2)",
    border: copiedResult ? "1px solid orange" : "1px solid rgba(0, 0, 0, 0.9)",
    borderRadius: "6px",
    color: copiedResult ? "#ff9800" : "#d1d5db",
    fontSize: "12px",
    cursor: "pointer",
    display: "flex",
    alignItems: "center",
    gap: "6px",
    transition: "all 0.2s"
  }} disabled={copiedResult}>
                  {copiedResult ? <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                      <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
                    </svg> : <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                      <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
                      <rect x="8" y="2" width="8" height="4" rx="1" ry="1" />
                    </svg>}
                  {copiedResult ? "Copied" : "Copy"}
                </button>
                {apiKey.trim() && <button onClick={downloadResults} style={{
    padding: "6px 12px",
    background: "rgba(255,255,255, 0.2)",
    border: "1px solid rgba(0, 0, 0, 0.9)",
    borderRadius: "6px",
    color: "#d1d5db",
    fontSize: "12px",
    cursor: "pointer",
    display: "flex",
    alignItems: "center",
    gap: "6px"
  }}>
                    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                      <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
                      <polyline strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} points="7,10 12,15 17,10" />
                      <line strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} x1="12" y1="15" x2="12" y2="3" />
                    </svg>
                    Download
                  </button>}
              </div>}
          </div>
          <div style={{
    background: "rgba(0,0,0,1.0)",
    border: "1px solid rgba(255,255,255,0.2)",
    borderRadius: "8px",
    padding: "16px",
    fontFamily: "'Monaco', 'Menlo', 'Ubuntu Mono', monospace",
    fontSize: "12px",
    lineHeight: "1.4",
    overflow: "auto",
    color: "#f9fafb",
    margin: 0,
    maxHeight: "400px",
    minHeight: "100px"
  }}>
            {renderJSON(result)}
          </div>
        </div>}

      {}
      {convertedQuery && apiKey.trim() && !result.startsWith("Error:") && <div style={{
    marginTop: "20px"
  }}>
          <div style={{
    display: "flex",
    alignItems: "center",
    justifyContent: "space-between",
    marginBottom: "12px"
  }}>
            <h4 style={{
    margin: 0,
    fontSize: "16px",
    fontWeight: "600"
  }}>
              Generated Query
            </h4>
                        <button onClick={copyConvertedQuery} style={{
    padding: "6px 12px",
    background: copiedQuery ? "rgba(255, 140, 0, 0.15)" : "rgba(255,255,255, 0.2)",
    border: copiedQuery ? "1px solid orange" : "1px solid rgba(0, 0, 0, 0.9)",
    borderRadius: "6px",
    color: copiedQuery ? "#ff9800" : "#d1d5db",
    fontSize: "12px",
    cursor: "pointer",
    display: "flex",
    alignItems: "center",
    gap: "6px",
    transition: "all 0.2s"
  }} disabled={copiedQuery}>
              {copiedQuery ? <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
                </svg> : <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                  <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
                  <rect x="8" y="2" width="8" height="4" rx="1" ry="1" />
                </svg>}
              {copiedQuery ? "Copied" : "Copy"}
            </button>
          </div>
          <div style={{
    background: "rgba(0,0,0,1.0)",
    border: "1px solid rgba(255,255,255,0.2)",
    borderRadius: "8px",
    padding: "16px",
    fontFamily: "'Monaco', 'Menlo', 'Ubuntu Mono', monospace",
    fontSize: "12px",
    lineHeight: "1.4",
    overflow: "auto",
    color: "#f9fafb",
    margin: 0,
    minHeight: "100px"
  }}>
            {renderJSON(convertedQuery)}
          </div>
        </div>}

      <style>{`
        @keyframes spin {
          0% { transform: rotate(0deg); }
          100% { transform: rotate(360deg); }
        }

        /* JSON Syntax Highlighting */
        .json-key {
          color: #7dd3fc; /* Light blue/cyan for keys */
        }

        .json-string {
          color: #fbbf24; /* Light orange/peach for strings */
        }

        .json-number {
          color: #86efac; /* Light green for numbers */
        }

        .json-boolean {
          color: #c084fc; /* Purple for booleans */
        }

        .json-null {
          color: #f87171; /* Red for null */
        }

        .json-punctuation {
          color: #ffffff; /* White for punctuation */
        }

        .json-error {
          color: #f87171; /* Red for error messages */
        }
      `}</style>
    </div>;
};

<QueryBuilder />

Resources:

* [API Query Translator](/docs/examples/query-translator)
* [API Query Tester](/docs/examples/api-query-tester)
