{"id":48708071,"url":"https://github.com/justintanner/apicity","last_synced_at":"2026-06-08T06:01:29.243Z","repository":{"id":339368585,"uuid":"1161638472","full_name":"justintanner/apicity","owner":"justintanner","description":"a thin api wrapper designed for llms","archived":false,"fork":false,"pushed_at":"2026-06-02T14:39:11.000Z","size":66288,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-02T15:18:34.987Z","etag":null,"topics":["ai","anthropic","api-client","cost-estimation","fal-ai","image-generation","llm","mcp","model-context-protocol","openai","typescript","video-generation","xai","zero-dependencies"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/justintanner.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":"ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-02-19T10:45:01.000Z","updated_at":"2026-06-02T14:50:26.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/justintanner/apicity","commit_stats":null,"previous_names":["justintanner/bareapi","justintanner/nakedapi","justintanner/apicity"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/justintanner/apicity","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/justintanner%2Fapicity","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/justintanner%2Fapicity/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/justintanner%2Fapicity/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/justintanner%2Fapicity/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/justintanner","download_url":"https://codeload.github.com/justintanner/apicity/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/justintanner%2Fapicity/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34050225,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-08T02:00:07.615Z","response_time":111,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","anthropic","api-client","cost-estimation","fal-ai","image-generation","llm","mcp","model-context-protocol","openai","typescript","video-generation","xai","zero-dependencies"],"created_at":"2026-04-11T12:54:19.002Z","updated_at":"2026-06-08T06:01:29.237Z","avatar_url":"https://github.com/justintanner.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# apicity\n\n[![CI](https://github.com/justintanner/apicity/actions/workflows/ci.yml/badge.svg)](https://github.com/justintanner/apicity/actions)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n[![TypeScript](https://img.shields.io/badge/TypeScript-strict-blue?logo=typescript\u0026logoColor=white)](tsconfig.base.json)\n[![Node](https://img.shields.io/badge/Node.js-%E2%89%A518-339933?logo=nodedotjs\u0026logoColor=white)](package.json)\n[![Zero Dependencies](https://img.shields.io/badge/provider_deps-0-brightgreen)](package.json)\n\nA thin wrapper for many APIs covering AI image generation, video generation, all major social media APIs, and more.\n\n## Features\n\n- **OTP pay gate — no bypass.** Paid endpoints (media generation, etc.) only fire with a human- or code-client-minted, single-use OTP bound to the exact request. An autonomous agent driving the API can't self-approve and can't run up your bill. [Details ↓](#paid-endpoints-otp-pay-gate)\n- **Pre-flight cost estimates.** Pure, local USD estimates for any call across every provider — no keys, no network.\n- **Schemas for agents.** Every POST endpoint validates its payload before sending, so a hallucinated call fails locally instead of at the API.\n- **MCP server.** Every endpoint exposed 1:1 as an MCP tool.\n- **Composable middleware.** `withRetry` / `withFallback` / `withRateLimit` as plain function wrappers.\n- **Zero provider dependencies.** Self-contained packages, ESM, strict TypeScript.\n\n## Example\n\nThe headline behavior: a **paid endpoint is gated**. Without an approved,\nsingle-use OTP the call fails closed — an autonomous caller cannot bypass it.\n\n```ts\nimport { createKie } from \"@apicity/kie\";\nimport { mintOtp, createCost } from \"@apicity/cost\";\n\n// The code client holds the pay-gate secret (from your secret manager / config).\n// The autonomous caller never sees it, so it can never self-approve a paid call.\nconst secret = loadSecret();\nconst kie = createKie({\n  apiKey: process.env.KIE_API_KEY!,\n  paygate: { secret },\n});\n\n// Same JSON body you'd POST to /api/v1/jobs/createTask.\nconst payload = {\n  model: \"gpt-image-2-text-to-image\",\n  input: {\n    prompt: \"A cinematic night-city poster with neon reflections.\",\n    aspect_ratio: \"16:9\",\n    resolution: \"4K\",\n  },\n};\n\n// Pure, local cost preview — no keys, no network, sync.\nconst estimate = createCost().estimate({ provider: \"kie\", payload });\n// estimate.usd === 0.08\n\n// Paid endpoint with no approval → fails closed. No bypass.\nawait kie.post.api.v1.jobs.createTask(payload);\n// ❌ throws PayGateError { code: \"otp-missing\" }\n\n// A human (or the code client) mints a single-use OTP, bound to THIS request.\nconst otp = mintOtp(secret, {\n  dotPath: \"api.v1.jobs.createTask\",\n  request: payload,\n  ttl: \"10m\",\n});\n\n// Approved — the generation runs once. Replaying the OTP, or changing any byte\n// of the payload, fails verification.\nconst task = await kie.post.api.v1.jobs.createTask(payload, { otp });\n```\n\nDirect KIE VEO calls are gated separately from `createTask`. For\n`kie.veo.post.api.v1.veo.generate`, mint the OTP with\n`dotPath: \"api.v1.veo.generate\"`; for\n`kie.veo.post.api.v1.veo.extend`, use `dotPath: \"api.v1.veo.extend\"`.\nUpload, status, and helper endpoints are unlisted and remain free.\n\n## Motivation\n\nMitigate the predicatble mistakes that AI Agents make when calls APIs such as:\n\n- Hallicinating JSON payloads or URLs\n- Calling APIs from weird locations and times\n- Wasting your expensive video and image gen tokens\n- And more\n\n## Packages\n\n| Package                                                           | Focus                                                              |\n| ----------------------------------------------------------------- | ------------------------------------------------------------------ |\n| [@apicity/openai](packages/provider/openai)                       | OpenAI chat, responses, images, audio, embeddings, files           |\n| [@apicity/anthropic](packages/provider/anthropic)                 | Anthropic messages, streams, batches, files, models, admin APIs    |\n| [@apicity/xai](packages/provider/xai)                             | xAI chat, responses, Grok images/video, files, collections, search |\n| [@apicity/fal](packages/provider/fal)                             | fal model registry, generation, pricing, usage, analytics          |\n| [@apicity/google](packages/provider/google)                       | Google Gemini express-mode generateContent                         |\n| [@apicity/kie](packages/provider/kie)                             | KIE media generation for video, image, audio, Claude, Suno         |\n| [@apicity/alibaba](packages/provider/alibaba)                     | Alibaba DashScope/Qwen chat, image, and video workflows            |\n| [@apicity/fireworks](packages/provider/fireworks)                 | Fireworks chat, embeddings, audio, deployments, fine-tuning        |\n| [@apicity/kimicoding](packages/provider/kimicoding)               | Kimi Coding messages, streaming, models, embeddings                |\n| [@apicity/elevenlabs](packages/provider/elevenlabs)               | ElevenLabs text-to-speech, sound effects, audio APIs               |\n| [@apicity/free-media-upload](packages/provider/free-media-upload) | Public file upload/hosting services                                |\n| [@apicity/x](packages/provider/x)                                 | X API posting and media upload                                     |\n| [@apicity/meta](packages/provider/meta)                           | Instagram Graph API reel publishing                                |\n| [@apicity/polymarket](packages/provider/polymarket)               | Polymarket Gamma, Data, and CLOB public market data                |\n| [@apicity/telegram](packages/provider/telegram)                   | Telegram Bot API text, photo, video, and audio sending             |\n| [@apicity/cost](packages/provider/cost)                           | Pure local cost/token estimates across providers                   |\n| [@apicity/mcp-server](packages/mcp-server)                        | MCP server exposing provider endpoints as tools                    |\n\n## Middleware\n\nEvery endpoint is a plain `(req, signal?) =\u003e Promise\u003cT\u003e` function, and every\npackage exports generic, function-level wrappers — `withRetry`, `withFallback`,\nand `withRateLimit` — that compose naturally. (`@apicity/kimicoding` also ships\n`withStreamRetry` / `withStreamFallback` for streamed async iterables.)\n\n### `withRetry` — exponential backoff\n\nRetries transient errors (HTTP 429 and 5xx) with configurable backoff.\n\n```ts\nimport { createOpenAi, withRetry } from \"@apicity/openai\";\n\nconst openai = createOpenAi({ apiKey: process.env.OPENAI_API_KEY! });\n\nconst chat = withRetry(openai.v1.chat.completions, {\n  retries: 3, // max attempts (default: 2)\n  baseMs: 500, // initial delay in ms (default: 300)\n  factor: 2, // exponential multiplier (default: 2)\n  jitter: true, // randomize delay ±20% (default: true)\n});\n```\n\n### `withFallback` — multi-provider failover\n\nTries each function in order; the next picks up when one fails. Wrappers return\nthe same signature, so they nest:\n\n```ts\nimport { createXai, withFallback, withRetry } from \"@apicity/xai\";\n\nconst primary = createXai({ apiKey: process.env.XAI_API_KEY_PRIMARY! });\nconst backup = createXai({ apiKey: process.env.XAI_API_KEY_BACKUP! });\n\nconst image = withFallback([\n  withRetry(primary.v1.images.generations, { retries: 2 }),\n  withRetry(backup.v1.images.generations, { retries: 1 }),\n]);\n\nconst result = await image({\n  model: \"grok-2-image\",\n  prompt: \"A product photo of a small brass desk lamp\",\n  n: 1,\n});\n```\n\n### `withRateLimit` — client-side throttling\n\nBounds requests-per-minute and concurrency through a shared limiter:\n\n```ts\nimport {\n  createOpenAi,\n  withRateLimit,\n  createRateLimiter,\n} from \"@apicity/openai\";\n\nconst openai = createOpenAi({ apiKey: process.env.OPENAI_API_KEY! });\nconst limiter = createRateLimiter({ rpm: 60, concurrent: 5 });\n\nconst chat = withRateLimit(openai.v1.chat.completions, limiter);\n```\n\nUse the wrappers each provider ships, or pass endpoint functions into your own\norchestration layer.\n\n## Development\n\n- **Runtime** — Node 18+, Cloudflare Workers, Deno, Bun. ESM only.\n- **Build \u0026 test** — `pnpm install \u0026\u0026 pnpm run build \u0026\u0026 pnpm run test:run`. Integration tests record/replay via Polly.js (no keys needed for replay).\n- **Validate before sending** — every POST endpoint exposes a `.schema`: `createOpenAi(...).v1.chat.completions.schema.safeParse(payload)` catches a hallucinated call locally instead of at the API.\n\n## Paid endpoints (OTP pay gate)\n\nEndpoints with direct marginal cost (e.g. `kie.post.api.v1.jobs.createTask` and\ndirect VEO calls under `kie.veo.post.api.v1.veo.*`) are listed in\n`PAID_ENDPOINTS` and gated behind a single-use OTP — the flow is the\n[example above](#example). The gate is **fail-closed**: a paid call cannot fire\nunless the provider was built with a pay-gate secret **and** the caller presents\na valid OTP minted from that same secret. The autonomous caller never sees the\nsecret, so it cannot self-approve. Unlisted endpoints are free.\n\nThe OTP is signed with a single shared **HMAC secret** — no key files, no\nenvironment variables, no cost coupling. It commits to the exact `(provider,\nmethod, dotPath, requestHash, exp)` tuple: change any byte of the payload and\nverification fails. The `jti` is consumed before dispatch, so a failed network\ncall still burns the token — mint a fresh OTP for any retry.\n\nOperators (or the code client) mint OTPs with `mintOtp(secret, { dotPath,\nrequest, ttl })` or the CLI — the secret is read from a file, never an env var:\n\n```bash\napicity-paygate otp mint \\\n  --secret-file ./paygate.secret \\\n  --dot-path api.v1.jobs.createTask \\\n  --payload-file request.json \\\n  --ttl 10m\n```\n\nA blocked call throws `PayGateError` whose `.code` is one of\n`paygate-not-configured`, `otp-missing`, `otp-malformed`,\n`otp-invalid-signature`, `otp-expired`, `otp-mismatched-request`, or\n`otp-replayed`.\n\nThe gate is generic — `xai` and others opt in by adding a `PAID_ENDPOINTS`\nentry. See [@apicity/cost](packages/provider/cost) for the full spec and the MCP\nserver's `--paygate-secret-file` wiring.\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n\nBased on [TetherAI](https://github.com/nbursa/TetherAI) by Nenad Bursac.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjustintanner%2Fapicity","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjustintanner%2Fapicity","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjustintanner%2Fapicity/lists"}