<< All versions

Skill v1.0.0

currentAutomated scan100/100
opensourceagi/qwksearch-research-agent/ask-qwksearch-api-client
──Details
PublishedOctober 21, 2026 at 10:08 AM
Content Hashsha256:6f9eb1bef4739b29...
Git SHA39953c932455
──Files
Files (1 file, 8.9 KB)
SKILL.md8.9 KBactive
SKILL.md · 136 lines · 8.9 KB

version: "1.0.0" name: ask-qwksearch-api-client description: Guide to qwksearch-api-client (packages/qwksearch-api-client), the hey-api generated TypeScript client for the QwkSearch backend — the 85 typed SDK functions, the shared client singleton and per-call client override, the api2client/grab transport and its cache/retry/rateLimit/mock options, the baseUrl resolution that differs between browser and server, throwOnError, and the two-step regeneration workflow from qwksearch-openapi.json. Use when calling the QwkSearch API from any app or package, when requests hit the wrong origin or 401, when a response carries no status or an unparsed error body, when a new endpoint is missing from the client, or when regenerating after an OpenAPI change.


Working With qwksearch-api-client

packages/qwksearch-api-client, published as qwksearch-api-client. Every file under src/ except ../baseurl.ts is generated by `@hey-api/openapi-ts` from qwksearch-openapi.json — do not hand-edit them, the next bun run build:api overwrites the lot.

src/client/client.gen.ts is generated twice over: openapi-ts writes Hey API's bundled fetch client there, then api2client --rewire-only ./src replaces it with a one-line re-export of api2client's client, so every operation sends through `grab`. That is why regeneration is bun run codegen, never bare openapi-ts — see Transport below.

Setup

ts
import { agentSearch, listChats, uploadFiles } from "qwksearch-api-client";
const { data, error } = await agentSearch({ query: { q: "…" } });

Every function takes one options object (body, path, query, headers, client, meta) and returns { data, error, request, response } — it does not throw by default. Pass throwOnError: true for exception-style flow.

baseUrl

baseurl.ts (referenced as runtimeConfigPath, so it runs at client construction) picks the origin per environment:

EnvironmentbaseUrl
Browserwindow.NEXT_PUBLIC_BASE_URL or location.origin, + /api
Server / Nodeprocess.env.NEXT_PUBLIC_BASE_URL or https://qwksearch.com, + /api

So in the browser the client talks to the current origin — which is what makes the same bundle work on localhost, staging and production — and on the server it needs NEXT_PUBLIC_BASE_URL or it goes to production.

Transport: api2client over grab

Requests do not go through fetch; they go through grab. The function signatures, options and { data, error, request, response } result are unchanged, so callers need no edits — but grab's options are now accepted per request and client-wide:

OptionEffect
cache, cacheForTimeServe repeats from grab's cache
retryAttemptsRetry a failed request
rateLimitMinimum seconds between calls to the same path
timeoutSeconds before abort
cancelOngoingIfNew, cancelNewIfOngoingDedupe concurrent calls to a path
debug, loggerLog request and response
grabSend on a custom instance, e.g. grab.instance({})
ts
await agentSearch({ query: { q }, cache: true, cacheForTime: 60, retryAttempts: 2 });

Three consequences worth knowing:

  • There is no `fetch` option. Passing one does nothing — grab owns that call. To

stub the transport in a test, replace globalThis.fetch, or register a mock by path: grab.mock["/agent/search"] = { response: { results: [] } }.

  • The status and the parsed error body need grab >= 1.6.23. api2client recovers the

raw Response through grab's onRawResponse hook and probes for it via grab.supports.onRawResponse. On an older grab there is no Response to hand back: result.response is undefined and result.error is grab's "HTTP error: <status>" string rather than the handler's JSON. packages/qwksearch-api-client/test/error-result-shape.test.ts skips itself on such a grab rather than assert what the transport cannot do.

  • SSE bypasses grab. agentChat and the rest of client.sse.* go straight to

fetch, because grab's cache/retry/timeout model assumes a request that completes.

Picking the right call

AreaFunctions
AgentagentChat, agentSearch, articleQa, articleFollowups, generateSuggestions, discoverContent, rewriteText, autocomplete
ChatslistChats, getChatById, searchChats, saveMessage, shareChat, deleteChatById, deleteAllChats
DocumentslistDocuments, createDocument, getDocumentById, updateDocument, deleteDocument, shareDocument, getSharedDocument
Articles & quotesgetArticle, updateArticle, listQuotes, createQuote, updateQuote, deleteQuote, listFavorites, addFavorite, removeFavorite
FilesuploadFiles, getUploadedFile, deleteUploadedFile, getUserStorage
VoicetranscribeAudio, textToSpeech
Config & modelsgetConfig, saveConfig, listProviders, addProvider, updateProvider, deleteProvider, addProviderModel, deleteProviderModel, testModels
MCPlistMcpServers, addMcpServer, updateMcpServer, toggleMcpServer, deleteMcpServer
Search infrastructurelistSearchEngines, getEngineStatus, updateEngineStatus, testSearchEngines, scrapeGet, scrapePost
Google Docs / DrivegoogleDocsAuth, googleDocsAuthStatus, googleDocsCallback, exportToGoogleDocs, importFromGoogleDocs, getGoogleDriveFile, shareGoogleDoc, getGoogleToken, refreshGoogleToken
NotebookLMnotebooklmLogin, getNotebooklmStatus, disconnectNotebooklm, listNotebooks, createNotebook, deleteNotebook, askNotebook, listNotebookSources, addNotebookSource, generateNotebookAudio
AccountsgetUser, updateUser, deleteUser, deleteUserAccount, changePassword, listSessions, revokeSession, revokeOtherSessions, listUserAccounts

The raw spec is also importable: qwksearch-api-client/openapi.json.

Recipes

Point at another backend. Either set NEXT_PUBLIC_BASE_URL, or build your own client and pass it per call:

ts
import { createClient, createConfig } from "qwksearch-api-client/client";
const client = createClient(createConfig({ baseUrl: "https://staging.example.com/api" }));
await listChats({ client });

client.setConfig({ headers: { Authorization: Bearer ${key} } }) sets auth for every call on that client — this is how the VS Code extension's API-key mode works.

Add an endpoint. Update qwksearch-openapi.json, run bun run build:api (openapi-ts && api2client --rewire-only ./src && vite build), and commit both src/*.gen.ts and dist/. This package has no `build` script, so the workspace prebuild chain skips it and the committed dist/ is what consumers get — forgetting to commit it ships a stale client. Running openapi-ts on its own leaves Hey API's fetch client in src/client/client.gen.ts; bun run codegen is the same two steps without the bundle.

Troubleshooting

SymptomCause → fix
Requests go to qwksearch.com from a local serverServer-side baseUrl falls back to production. Set NEXT_PUBLIC_BASE_URL.
Requests 404 with a doubled /api/api/...baseUrl already appends /api; the generated paths are relative to it. Do not add it again.
No exception on a failed callthrowOnError defaults to false — read error from the result, or pass throwOnError: true.
401 everywhere from a non-browser callerBrowser calls carry the session cookie automatically; a server/CLI caller must set an Authorization header on its own client.
A generated file was edited and the change vanishedsrc/**/*.gen.ts is regenerated wholesale. Put customisation in baseurl.ts, or change the OpenAPI spec.
A new backend endpoint is missingThe spec is a committed snapshot, not fetched at build time. Update qwksearch-openapi.json and regenerate.
Consumers see an old client after your changeThere is no build script, so nothing rebuilds it in the workspace chain. Run bun run build:api and commit dist/.
Types don't resolve (TS7016)dist/index.d.ts is missing. vite-plugin-dts 5 spells the bundle option bundleTypes (not rollupTypes) and needs @microsoft/api-extractor present, or it silently emits per-file types under dist/src/ instead. Run bun run build:api and check dist/index.d.ts exists.
result.response is undefined on a failed callThe installed grab predates onRawResponse (< 1.6.23), so api2client has no Response to return. Check grab.supports?.onRawResponse.
result.error is "HTTP error: 500" instead of the handler's JSONSame cause as the row above — the error body is only read when the raw Response comes back.
A fetch passed in the client config is ignoredThere is no fetch option under api2client. Stub globalThis.fetch or use grab.mock.
All versions