DocsQuickstart & API
API reference

Company API integration

Use this guide to connect a website, mobile app, or backend to a company-scoped Querix search endpoint.

Querix API · v1 contractLast reviewed July 2026

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.

Verify endpoint access
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.

StatusMeaning and action
401Missing or invalid API key
403Key belongs to a different tenant endpoint
404Unknown 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.

GET/ready

Public readiness

Checks configured tenant indexes, source databases, and the embedding runtime; returns 503 when a critical serving dependency is unavailable.

GET/{tenant}/health

Tenant health

Authenticated operational view, including indexed count and runtime status.

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.

Typed browser client
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.

Session-safe UI flow
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.

First page
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 roleType or storeContract
querystringRequired on first page; natural-language query, maximum 1000 characters.
cursorstringRequired for the next page; opaque, tenant-bound, short-lived.
page_sizeintegerOptional; defaults to 20 and is limited by server policy.
Next page
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.

Response shape
{
  "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.

StatusMeaning and action
400Invalid cursor. Restart with the original query.
410Expired cursor. Clear pagination state and search again.
422Invalid shape, blank query, unsupported field, or invalid page size.
429Rate policy reached. Honor Retry-After and retry with backoff.
503A 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.

Monthly usage
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.

Need help with your integration?

Talk to us about your catalog and search contract.

Contact engineering