Company API integration
Use this guide to connect a website, mobile app, or backend to a company-scoped Querix search endpoint.
01
Base URL and authentication
Production APIs are served over HTTPS at https://api.querix.co/api/v1. Each tenant receives an endpoint slug and issued API key. The generic search endpoint is POST /api/v1/{tenant}/search.
curl -sS https://api.querix.co/api/v1/<tenant>/auth/verify \
-H "X-API-Key: <tenant-api-key>"Credential boundary
Do not place a tenant key in browser source, query parameters, or public configuration. Route browser calls through your server or another controlled credential boundary.
| Status | Meaning and action |
|---|---|
| 401 | Missing or invalid API key |
| 403 | Key belongs to a different tenant endpoint |
| 404 | Unknown or disabled tenant endpoint |
02
Readiness and tenant health
Use readiness to confirm that critical serving dependencies are available across the configured tenants. Use authenticated tenant health for the index, cache, embedding, and runtime detail behind one company search experience.
/readyPublic readiness
Checks configured tenant indexes, source databases, and the embedding runtime; returns 503 when a critical serving dependency is unavailable.
/{tenant}/healthTenant health
Authenticated operational view, including indexed count and runtime status.
03
Recommended browser integration architecture
A browser should call your application backend, not Querix directly. Your backend is the credential boundary: it validates the incoming request, adds the tenant API key from server secrets, calls Querix, and returns only the intended response to the browser. This keeps a permanent tenant credential out of JavaScript bundles, browser storage, logs, and network tools available to end users.
- 01
Browser search UI
- 02
Your authenticated backend route
- 03
Tenant-bound Querix endpoint
- 04
Public mapped search response
Server-side integration example
Choose the backend your team uses. Every version keeps the tenant key server-side.
type SearchInput = { query?: string; cursor?: string; page_size?: number };
export async function POST(request: Request) {
const input = (await request.json()) as SearchInput;
const hasQuery = Boolean(input.query?.trim());
const hasCursor = Boolean(input.cursor);
if (hasQuery === hasCursor) {
return Response.json({ error: "Send exactly one of query or cursor." }, { status: 422 });
}
const upstream = await fetch("https://api.querix.co/api/v1/<tenant>/search", {
method: "POST",
headers: { "content-type": "application/json", "X-API-Key": process.env.QUERIX_TENANT_API_KEY! },
body: JSON.stringify(hasQuery ? { query: input.query!.trim(), page_size: 20 } : { cursor: input.cursor, page_size: 20 }),
});
return new Response(upstream.body, { status: upstream.status, headers: { "content-type": "application/json" } });
}Do not use a browser-visible tenant key
Do not put X-API-Key in React/Vite environment variables, local storage, cookies readable by JavaScript, public configuration, query strings, or a mobile bundle. The browser receives search results, never the tenant credential.
04
Browser request and response contract
Keep browser state small and explicit: the original query, current items, next cursor, loading state, and a user-safe error message. The cursor belongs to the current query only; changing a query, sort, or explicit filter must clear it and start a new session.
type SearchItem = { id: string | number; title: string; [field: string]: unknown };
type SearchPage = {
items: SearchItem[];
pagination: { has_more: boolean; next_cursor: string | null };
interpreted_query?: { execution_path?: string };
timings_ms?: { total?: number };
};
async function searchCatalog(
input: { query?: string; cursor?: string; pageSize?: number },
signal?: AbortSignal,
): Promise<SearchPage> {
const response = await fetch("/api/search", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
...(input.query ? { query: input.query } : { cursor: input.cursor }),
page_size: input.pageSize ?? 20,
}),
signal,
});
const payload = await response.json();
if (!response.ok) throw Object.assign(new Error("Search request failed"), { status: response.status, payload });
return payload as SearchPage;
}05
Interaction lifecycle: typing, submit, and next page
Treat a search result as a session. Start a new session on form submit or after a modest debounce if you offer search-as-you-type. Cancel an older request when a newer query starts so stale results cannot overwrite the latest screen. Do not fetch the next page until the first response has supplied a non-null next_cursor.
let activeController: AbortController | undefined;
let nextCursor: string | null = null;
async function startSearch(query: string) {
activeController?.abort();
activeController = new AbortController();
nextCursor = null;
setSearchState({ phase: "loading", query, items: [], error: null });
try {
const page = await searchCatalog({ query }, activeController.signal);
nextCursor = page.pagination.next_cursor;
setSearchState({ phase: "ready", query, items: page.items, error: null });
} catch (error) {
if ((error as Error).name !== "AbortError") handleSearchError(error);
}
}
async function loadNextPage() {
if (!nextCursor || searchState.phase === "loading-more") return;
setSearchState({ ...searchState, phase: "loading-more" });
const page = await searchCatalog({ cursor: nextCursor });
nextCursor = page.pagination.next_cursor;
setSearchState({ ...searchState, phase: "ready", items: [...searchState.items, ...page.items] });
}- Trim and reject blank input before making a request.
- Use a 250-300 ms debounce only for deliberate search-as-you-type behavior; always keep a submit action.
- Abort previous in-flight search requests when query text changes.
- Disable duplicate next-page requests while a page is loading.
- Reset scroll/list state when a new query starts; append only pages that belong to the same cursor session.
06
Generic search contract
Send exactly one of query or cursor. A query creates a bounded result session; a cursor advances the same session without repeating semantic work.
curl -sS -X POST https://api.querix.co/api/v1/<tenant>/search \
-H "Content-Type: application/json" \
-H "X-API-Key: <tenant-api-key>" \
-d '{ "query": "bike in Chennai under 1000", "page_size": 20 }'| Field or role | Type or store | Contract |
|---|---|---|
| query | string | Required on first page; natural-language query, maximum 1000 characters. |
| cursor | string | Required for the next page; opaque, tenant-bound, short-lived. |
| page_size | integer | Optional; defaults to 20 and is limited by server policy. |
curl -sS -X POST https://api.querix.co/api/v1/<tenant>/search \
-H "Content-Type: application/json" \
-H "X-API-Key: <tenant-api-key>" \
-d '{ "cursor": "<next_cursor>", "page_size": 20 }'07
Response, pagination, and diagnostics
Search responses include public result fields, interpreted intent, safely-applied filters, timings, usage accounting, and pagination. Internal embedding content, credentials, raw provider payloads, and private database columns are never part of the public response.
{
"company_id": "<tenant>",
"items": [{ "id": 235255, "title": "Mountain bike" }],
"interpreted_query": { "execution_path": "semantic" },
"applied_filters": { "max_rental_fee": 1000 },
"timings_ms": { "total": 118.9 },
"pagination": { "returned": 20, "has_more": true, "next_cursor": "..." }
}Cursor discipline
Never decode, alter, or store a cursor long-term. On an expired cursor, clear pagination state and repeat the original query.
08
Client error behavior and launch checks
A tenant can map its existing request field names to the standard search contract. Unexpected fields are rejected, so frontend payloads should match the provisioned contract exactly.
| Status | Meaning and action |
|---|---|
| 400 | Invalid cursor. Restart with the original query. |
| 410 | Expired cursor. Clear pagination state and search again. |
| 422 | Invalid shape, blank query, unsupported field, or invalid page size. |
| 429 | Rate policy reached. Honor Retry-After and retry with backoff. |
| 503 | A runtime dependency is unavailable. Show temporary failure and retry with jitter. |
- Endpoint slug and API key are provisioned.
- Auth verification and tenant health return success.
- First page and subsequent-page behavior are tested.
- Public fields match the UI, and no key is exposed client-side.
- The client handles 401, 403, 410, 422, 429, and 5xx responses.
- The browser only calls your backend proxy and never receives a permanent tenant key.
- Cancelled or stale requests cannot replace the most recent query results.
09
Usage and protected diagnostics
When usage tracking is enabled, tenants can retrieve month-scoped usage. Operator diagnostics use a separate admin credential and deliberately return privacy-safe summaries rather than raw query text, result payloads, or secrets.
curl -sS 'https://api.querix.co/api/v1/<tenant>/usage?month=YYYY-MM' \
-H "X-API-Key: <tenant-api-key>"Interactive OpenAPI documentation can be made available by deployment policy at /docs, with the machine-readable schema at /openapi.json.