All payments in preview are in test mode. Use card 4242 4242 4242 4242.

Official SDKs

Single-file, zero-dependency clients for the ToolHund AI Tool Intelligence API. MIT licensed. Drop them into any project and start shipping.

TypeScript / JavaScript

Runs on Node 18+, Deno, Bun, Cloudflare Workers, and modern browsers. No dependencies.

Install
curl -o toolhund.ts https://toolhund.com/api/public/sdk/toolhund/ts
Usage
import { ToolHund } from "./toolhund";

const th = new ToolHund({ apiKey: process.env.TH_KEY! });
const { data } = await th.tools.list({ q: "agents", limit: 10 });
console.log(data);
View full source (toolhund.ts)
toolhund.ts
// toolhund.ts — Official TypeScript SDK for the ToolHund API
// Zero dependencies. Works in Node 18+, Deno, Bun, Cloudflare Workers, and modern browsers.
// Docs: https://toolhund.com/api-docs

export interface ToolHundOptions {
  apiKey: string;
  baseUrl?: string;
  fetch?: typeof fetch;
}

export interface ToolHundError extends Error {
  status: number;
  code?: string;
  requestId?: string;
}

export class ToolHund {
  private key: string;
  private base: string;
  private f: typeof fetch;

  constructor(opts: ToolHundOptions) {
    if (!opts.apiKey) throw new Error("ToolHund: apiKey is required");
    this.key = opts.apiKey;
    this.base = (opts.baseUrl ?? "https://toolhund.com").replace(/\/$/, "");
    this.f = opts.fetch ?? fetch;
  }

  private async req<T>(method: string, path: string, query?: Record<string, unknown>, body?: unknown): Promise<T> {
    const url = new URL(this.base + path);
    if (query) for (const [k, v] of Object.entries(query)) if (v != null) url.searchParams.set(k, String(v));
    const res = await this.f(url.toString(), {
      method,
      headers: {
        "Authorization": `Bearer ${this.key}`,
        "Accept": "application/json",
        ...(body ? { "Content-Type": "application/json" } : {}),
      },
      body: body ? JSON.stringify(body) : undefined,
    });
    const text = await res.text();
    const json = text ? JSON.parse(text) : null;
    if (!res.ok) {
      const err = new Error(json?.error?.message ?? `HTTP ${res.status}`) as ToolHundError;
      err.status = res.status;
      err.code = json?.error?.code;
      err.requestId = res.headers.get("x-request-id") ?? undefined;
      throw err;
    }
    return json as T;
  }

  tools = {
    list: (params?: { q?: string; category?: string; limit?: number; offset?: number }) =>
      this.req<{ data: any[]; total: number }>("GET", "/api/v1/tools", params),
    get: (slug: string) => this.req<any>("GET", `/api/v1/tools/${slug}`),
    alternatives: (slug: string) => this.req<any>("GET", `/api/v1/tools/${slug}/alternatives`),
    reviews: (slug: string, params?: { limit?: number; offset?: number }) =>
      this.req<any>("GET", `/api/v1/tools/${slug}/reviews`, params),
    benchmarks: (slug: string) => this.req<any>("GET", `/api/v1/tools/${slug}/benchmarks`),
  };

  categories = { list: () => this.req<any>("GET", "/api/v1/categories") };
  deals = { list: () => this.req<any>("GET", "/api/v1/deals") };
  news = { list: () => this.req<any>("GET", "/api/v1/news") };
  stacks = {
    build: (input: { task: string; budget?: string; team_size?: number }) =>
      this.req<any>("POST", "/api/v1/stacks/build", undefined, input),
  };
}

export default ToolHund;

Python

Python 3.8+. Depends on requests.

Install
curl -o toolhund.py https://toolhund.com/api/public/sdk/toolhund/py
pip install requests
Usage
from toolhund import ToolHund
th = ToolHund(api_key="th_live_...")
print(th.list_tools(q="agents", limit=10))
View full source (toolhund.py)
toolhund.py
"""toolhund.py — Official Python SDK for the ToolHund API.

Requires: requests (pip install requests)
Docs: https://toolhund.com/api-docs
"""
from __future__ import annotations
from typing import Any, Optional, Dict
import requests


class ToolHundError(Exception):
    def __init__(self, message: str, status: int, code: Optional[str] = None, request_id: Optional[str] = None):
        super().__init__(message)
        self.status = status
        self.code = code
        self.request_id = request_id


class ToolHund:
    def __init__(self, api_key: str, base_url: str = "https://toolhund.com"):
        if not api_key:
            raise ValueError("api_key is required")
        self.key = api_key
        self.base = base_url.rstrip("/")
        self.s = requests.Session()
        self.s.headers.update({"Authorization": f"Bearer {api_key}", "Accept": "application/json"})

    def _req(self, method: str, path: str, params: Optional[Dict[str, Any]] = None, json: Optional[Any] = None) -> Any:
        r = self.s.request(method, self.base + path, params=params, json=json, timeout=30)
        try:
            data = r.json() if r.text else None
        except ValueError:
            data = None
        if not r.ok:
            err = (data or {}).get("error") or {}
            raise ToolHundError(err.get("message", f"HTTP {r.status_code}"), r.status_code,
                                err.get("code"), r.headers.get("x-request-id"))
        return data

    # Tools
    def list_tools(self, q=None, category=None, limit=None, offset=None):
        return self._req("GET", "/api/v1/tools", {"q": q, "category": category, "limit": limit, "offset": offset})
    def get_tool(self, slug: str): return self._req("GET", f"/api/v1/tools/{slug}")
    def alternatives(self, slug: str): return self._req("GET", f"/api/v1/tools/{slug}/alternatives")
    def reviews(self, slug: str, limit=None, offset=None):
        return self._req("GET", f"/api/v1/tools/{slug}/reviews", {"limit": limit, "offset": offset})
    def benchmarks(self, slug: str): return self._req("GET", f"/api/v1/tools/{slug}/benchmarks")

    # Discovery
    def categories(self): return self._req("GET", "/api/v1/categories")
    def deals(self): return self._req("GET", "/api/v1/deals")
    def news(self): return self._req("GET", "/api/v1/news")

    # Stack builder
    def build_stack(self, task: str, budget: Optional[str] = None, team_size: Optional[int] = None):
        payload = {"task": task}
        if budget: payload["budget"] = budget
        if team_size: payload["team_size"] = team_size
        return self._req("POST", "/api/v1/stacks/build", json=payload)

Other languages

The API is plain REST with Bearer auth. Generate a client in any language from our OpenAPI 3 spec using tools like openapi-generator, oapi-codegen, or orval.

npx @openapitools/openapi-generator-cli generate \
  -i https://toolhund.com/openapi.json \
  -g go \
  -o ./toolhund-go