Skip to content
All articles

Askolia for Convex: search, sourced answers and document sync

Appik Studio

The first version of @appik-studio/askolia-convex is available on npm. Version 0.1.0 connects a Convex application to Askolia for search, AI answers with sources and synchronization of public documents. The package uses the MIT license.

You record a synchronization intent in the mutation that changes your data. The component delivers it to Askolia after the transaction commits, tracks its status and retries eligible failures. Search and question answering run from your Convex actions.

Install and register the component

You need Convex 1.46 or newer and an Askolia backend with the matching integration API deployed. Install the package in your Convex application:

bun add @appik-studio/askolia-convex@0.1.0 convex@^1.46.0

Register the component and pass its server environment variables:

// convex/convex.config.ts
import { defineApp } from "convex/server";
import { v } from "convex/values";
import askolia from "@appik-studio/askolia-convex/convex.config.js";

const app = defineApp({
  env: {
    ASKOLIA_URL: v.optional(v.string()),
    ASKOLIA_API_KEY: v.optional(v.string()),
  },
});
app.use(askolia, {
  env: {
    ASKOLIA_URL: app.env.ASKOLIA_URL,
    ASKOLIA_API_KEY: app.env.ASKOLIA_API_KEY,
  },
});
export default app;

For hosted Askolia, set ASKOLIA_URL to https://tacit-goose-489.eu-west-1.convex.site. If you operate your own Askolia backend, use its HTTPS HTTP actions origin. The dashboard address and the .convex.cloud address are not the HTTP actions origin.

To obtain a hosted integration key, contact Appik Studio with your Askolia project name. Hosted key provisioning is currently assisted; the dashboard has no integration-key controls yet. Set the supplied ASKOLIA_API_KEY in your Convex deployment environment and keep it on the server. Operators of their own Askolia backend can create a token with the authenticated mutation integrationApi:createKey, passing projectId and label. The mutation returns the token once. This is an Askolia management function, not a function to add to your consuming app. A widget public key, Convex admin key or session cookie cannot replace it.

Use a separate Askolia project and integration key for every development, preview and production deployment. Never copy a production token into preview defaults. Installation without credentials succeeds, but requests fail.

Askolia bills per project, so additional environment projects can add subscription costs. Leave credentials unset in previews that do not need the integration. For isolated automated tests, use the package's exported test helper with mocked transport.

Create a client after Convex generates the component types:

// convex/askolia.ts
import { Askolia } from "@appik-studio/askolia-convex";
import { components } from "./_generated/api";

export const askolia = new Askolia(components.askolia);

Search and ask from actions

Inside a Convex action, call the client with the action context:

const matches = await askolia.search(ctx, {
  query: "How long does delivery take?",
  locale: "en",
  limit: 5,
});

const result = await askolia.ask(ctx, {
  query: "How long does delivery take?",
  locale: "en",
  clientId: "visitor_12345678",
});

if (result.kind === "answer") {
  console.log(result.answer, result.sources);
}

search returns records with _id, question, answer and optional group and url. ask returns one of answer, abstain or unavailable. Display the returned sources with an answer, and handle the other outcomes without inventing an answer. The component supports en, fr, de, es and it.

Queries contain 2–200 characters. Search accepts a limit of 1–20 results. Use a stable clientId of 8–200 characters, with letters, digits, dots, underscores, colons or hyphens. The example identifier is illustrative; supply the identifier your application assigns to the caller.

Authorize your host functions before exposing them to clients. AI answers remain subject to the Askolia project's plan, monthly allowance and rate limits.

Synchronize within your mutations

In a mutation that creates an article, write the record and synchronization intent together. Here, title and content are the article fields accepted by your mutation:

const id = await ctx.db.insert("articles", { title, content });
const revision = await askolia.syncDocument(ctx, {
  key: id,
  title,
  content,
});

Reuse that stable key for later updates. If the host mutation fails, the database write, intent and scheduled worker all roll back. Delivery to Askolia happens after commit and is eventually consistent.

In the mutation that deletes the article, record the removal too:

await ctx.db.delete(articleId);
await askolia.removeDocument(ctx, { key: articleId });

Only synchronize content intended for public search. Documents join the Askolia project's published knowledge and can appear in public search and AI answers. V1 does not synchronize permissions or private user data.

Follow delivery and retry failures

Expose an authorized, reactive host query that calls:

const state = await askolia.getSyncState(ctx, { key: articleId });

It returns null before the first intent. Afterwards, inspect revision and status together. Status is pending, syncing, synced or failed. The state also includes attempts, scheduled retry time and a sanitized error when present. synced means Askolia acknowledged the document change. It does not mean embeddings or AI answers are ready. Older success metadata can remain while a newer revision is pending.

Network errors, HTTP 408 and 5xx responses retry up to eight attempts. Delays start at five seconds and double up to five minutes. HTTP 429 responses wait 60–75 seconds without consuming the attempt budget. Other HTTP errors stop immediately. A lease and scheduled watchdog recover interrupted workers.

After fixing credentials or a delivery problem, call askolia.retrySync(ctx, { key: articleId }) from a mutation. It retries the same revision with a fresh budget. For corrected content, call syncDocument instead. An authorized query can use listFailed with paginationOpts to list failures in pages of at most ten states.

Namespaces and the limits of V1

Each component instance has a persistent namespace. The same document key in another namespace identifies a different document. Increasing revisions, idempotent retries and deletion tombstones prevent a delayed older write from restoring deleted content. Reinstalling with a new namespace does not adopt or remove the former instance's documents.

Namespaces separate document identities. They do not create separate public search collections within one Askolia project. Every namespace using that project's keys contributes to its public knowledge and mapping limit. Deleting a preview deployment or revoking its key does not remove documents or reclaim mapping slots.

Before integrating, account for these V1 boundaries:

  • Documents contain text or Markdown, with a title of 1–200 characters and content of 1–100,000 UTF-8 bytes. Keys must be nonblank and at most 200 characters. There is no PDF extraction or arbitrary file upload.
  • Synchronized documents have title attribution but no source URL in this version.
  • ask handles one question per call. It does not create a support conversation or maintain a conversation thread.
  • Synchronization runs from your app to Askolia. Dashboard edits do not flow back. A new sync revision can republish host content; an exact retry preserves its original acknowledgment.
  • Each component instance accepts 2,000 distinct keys, including deletion tombstones. Deletion does not reclaim slots. There is no automatic garbage collection or namespace reset.
  • The server accepts 2,000 document mappings and ten active API keys per project. The separate shared 500-document limit still applies. Search and ask share a 120-request-per-minute bucket; document writes have their own 120-request-per-minute bucket.

Start with an isolated development project and a few public documents. Check delivery state, search results and answer sources before connecting production content. The package README documents configuration, validation rules, failure recovery and the test helper. The Convex component documentation explains how components fit into a Convex app.