Official typed SDKs for AnyAPI: any API, one wallet, USD, no subscriptions. Reach hundreds of scraping and data APIs through one interface and one key; pay per request in real US dollars.
- TypeScript:
@getanyapi/sdk(npm) - zero runtime deps, ESM + CJS, Node 18+ and edge runtimes. - Python:
getanyapi(PyPI) - httpx + pydantic v2, Python 3.10+, sync and async clients.
Both packages are generated from the platform's own /openapi.json (222 SKUs), so they
track the catalog automatically.
TypeScript:
import { AnyAPI } from "@getanyapi/sdk";
const client = new AnyAPI({ apiKey: process.env.ANYAPI_API_KEY });
const res = await client.google.search({ query: "best coffee maker" });
if (res.output.found) console.log(res.output.data, res.costUsd);Python:
from getanyapi import AnyAPI
client = AnyAPI() # reads ANYAPI_API_KEY from the environment
res = client.google.search(query="best coffee maker")
if res.output.found:
print(res.output.data, res.cost_usd)Both packages share one surface; the per-package READMEs (TypeScript, Python) have full detail. The essentials:
- Not found vs error. A success always resolves. Most SKUs wrap the payload in a
foundflag (output.foundisfalsewhen the upstream had no match, which is not an error).unwrap(res)returns the data or throwsResultNotFoundError(a subclass ofNotFoundError) on an empty result. A few SKUs return their data object directly asoutput;unwrapreturns it as-is and never throws. - Pagination. Paginated SKUs expose an iterator (
client.reddit.iterSearch(...)/client.reddit.iter_search(...)) that yields items across pages and follows the cursor. Call.pages()on it to walk whole results and read each page'scostUsd/cost_usd. - Request options.
fields,maxItems/max_items, andsummarytrim the response to save context/bandwidth; they do NOT change the price. Per-calltimeout(Ms)and retry overrides live here too. - Errors and retries. Every failure raises an
AnyAPIErrorsubclass mapped from the HTTP status (400/401/402/404/429/502, plusConnection/Timeoutat status 0). Only 429 and network failures retry, with jittered backoff honoringRetry-After; timeouts never retry. DefaultmaxRetries/max_retriesis 2. - Agent signup.
agentSignup()/agent_signup()bootstraps a capped starter key with no account, for autonomous agents; a human funds it via the returned claim URL.
SPEC.md- the frozen contract (IR shape, naming rules, runtime signatures). Read this before touching anything.openapi.json+catalog.json- committed snapshots of the live gateway (the generator inputs: schemas/pricing ceiling from openapi, category/per-item pricing from catalog). Both are verbatim upstream artifacts and the only files exempt from the dash guard.generator/- internal codegen (not published). It reads the two snapshots, emits a deterministicir.json(the normalized intermediate the emitters consume), afixtures.json(one synthetic run-response per SKU, used by the integration tests), and the two generated SDK trees.ir.jsonandfixtures.jsonare committed.packages/typescript/- the@getanyapi/sdkpackage. Handwritten runtime insrc/core/; emitted namespaces, sku-map, and client insrc/generated/.packages/python/- thegetanyapipackage. Handwritten runtime insrc/getanyapi/(_*.pytypes.py); emitted namespaces insrc/getanyapi/platforms/.
openapi.json + catalog.json (committed upstream snapshots)
| generator/src/ir.ts (extractor: envelope crack, naming, USD pricing,
v dash normalization, pagination detection)
ir.json (deterministic, sorted, committed)
| emit-ts.ts / emit-py.ts / fixtures.ts
v
packages/*/src/generated/** + generator/fixtures.json
The emitters read ONLY ir.json, never the raw OpenAPI. pnpm generate runs the whole
pipeline; pnpm generate:check regenerates in memory and byte-compares against the
committed output (the CI drift gate). Two runs on the same snapshots are byte-identical.
To refresh from the live gateway: pnpm --filter @anyapi/generator fetch then
pnpm generate.
pnpm install
pnpm generate # snapshots -> ir.json -> fixtures.json + both generated trees
pnpm check # dash guard + drift check + tsc/vitest/tsup (TS) across packagesPython side (its gates run in CI separately from pnpm check):
pip install -e "packages/python[dev]"
cd packages/python && pyright && mypy && pytestpnpm check and the CI suite are mock-only: they never touch the registries. The
published-artifact smoke fills that gap. It installs BOTH SDKs FROM the registries (npm +
PyPI) into throwaway temp dirs outside the repo, mints an ephemeral capped key via the public
/agent/signup endpoint (no secrets needed), and makes ONE real production call through each
(a $0.001 reddit.search), then asserts a well-formed, non-empty envelope. This catches
packaging bugs, missing files, broken exports or types, that source-tree tests cannot see.
bash scripts/smoke.sh # smoke the latest published version
VERSION=0.1.0 bash scripts/smoke.shIt exits nonzero if either SDK fails and prints a per-SDK PASS/FAIL summary. In CI it is a
standalone workflow (.github/workflows/smoke.yml) that runs nightly (08:00 UTC) and on manual
dispatch (with an optional version input). It is intentionally NOT wired into ci.yml,
release.yml, or branch protection, so registry or upstream flakiness never blocks a PR.
Releases are automated from the live catalog. Two workflows drive it:
-
.github/workflows/regen.yml(auto-bump) runs on arepository_dispatchof typesdk-refresh(the AnyAPI monorepo fires this after each gateway deploy), a nightly cron fallback, and manual dispatch. It fetches the liveopenapi.json+catalog.json, regenerates the whole tree, and classifies the IR diff:- a new SKU, a new input/output field, a new enum member, or a new platform -> minor;
- a removed SKU, field, or enum member -> patch, but the commit body WARNS loudly (removals are deferred to a scheduled major, never auto-bumped to major here);
- pricing, descriptions, pagination flips, doc-only churn -> patch;
- no SKU-surface change (snapshot metadata only) -> none (no commit, loop-safe).
On a non-
nonebump it applies the version to BOTHpackages/typescript/package.jsonandpackages/python/pyproject.tomlin lockstep, commits the regenerated tree, tagsv<X.Y.Z>, pushes, and dispatchesrelease.yml. The change summary (the classifier output) is the commit body and becomes the GitHub Release notes. -
.github/workflows/release.yml(publish) runs on av*tag push and on manual dispatch (taginput).verifyre-runs the full gate suite and asserts the tag matches both manifests; thenpublish-npmpublishes@getanyapi/sdkwith--provenance,publish-pypipublishesgetanyapivia PyPI trusted publishing (OIDC,pypienvironment), andgithub-releasecuts the Release.
The version starts at 0.0.0; the first publish is manual because there is no prior tag.
- Bump both manifests in lockstep and commit on
main:pnpm --filter @anyapi/generator run version:apply 0.1.0(edits both files), then commit. - Create and push the tag:
git tag v0.1.0 && git push origin v0.1.0. The tag push triggersrelease.yml(a human-pushed tag fires thepushtrigger, unlike a bot-pushed one). Or dispatch it:gh workflow run release.yml -f tag=v0.1.0. verifyruns the full gate and the version match, then npm and PyPI publish in parallel.
The npm publish works immediately (the NPM_TOKEN secret is set on the repo). The PyPI
publish requires the trusted publisher to exist first (see next).
Trusted publishing has no API token; it authorizes this repo's workflow via OIDC. Until it is
configured, publish-pypi fails with a clear "trusted publisher not configured" message
(that is expected, and we do NOT fall back to a token). Configure it once on PyPI:
-
For a project that does not exist yet, add a pending publisher at https://pypi.org/manage/account/publishing/; for an existing project use its Settings -> Publishing page. Fill in:
- PyPI Project Name:
getanyapi - Owner:
getanyapi-com - Repository name:
sdks - Workflow name:
release.yml - Environment name:
pypi
These must match
release.ymlexactly (thepublish-pypijob runs inenvironment: pypi). Re-run the failedpublish-pypijob (or the whole release) once the publisher is saved. - PyPI Project Name:
- Prices are always USD. Credits (an internal unit) never appear in the SDK.
- The provider is always
"AnyAPI". Upstream backends are never named. - ASCII hyphen only: no em dashes or en dashes anywhere except the upstream
openapi.jsonsnapshot.
Generated code is committed and carries a Generated - do not edit header. Edit the
generator, not the output.
MIT