@veriax/sdk · v0.0.5
VERIAX JavaScript SDK
@veriax/sdk lets any JavaScript application, in the browser or in a native app, use VERIAX agents. Your application can hold a conversation with an agent, let it use its tools, ask it for structured output built from your knowledge, send it files and get back what it produces.
Your server keeps the API key and asks VERIAX for short-lived tokens; your application works only with those tokens. A token can act in one of three ways:
- as an application account you create for the integration, for example to serve anonymous visitors;
- as your own VERIAX user, for an integration that works on your behalf;
- as each of your users, when your application signs its users in and VERIAX knows them by their email.
Choose how your application signs in explains the three cases.
Getting started
This tutorial goes from an empty project to a working conversation. You need a VERIAX account and at least one agent.
1. Create an API key
Every integration starts from an API key. Where you create it depends on who the tokens will act as; for a first test, use your own user:
- In VERIAX, open your Profile.
- In the Access credentials card, open the API keys tab and choose Create API key.
- Give the key a name and an expiry date, then create it.
- Copy the key: it is shown only once.
For the other two cases see Choose how your application signs in. Keep the key on your server, in an environment variable or a secret manager.
2. Install the SDK
npm install @veriax/sdk
The package is ESM only and has no runtime dependencies.
3. Issue tokens from your server
Add an endpoint to your server that asks VERIAX for a token and returns it to your application. VERIAX answers 201 {"token", "expires_at", "expires_in"}: return that body as is, with Cache-Control: no-store.
# Django: settings.VERIAX_URL and settings.VERIAX_API_KEY come from the environment
import requests
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
from django.views.decorators.http import require_POST
@require_POST
@login_required
def veriax_token(request):
answer = requests.post(
f"{settings.VERIAX_URL}/api/v1/ai/get-token/",
headers={"Authorization": f"Api-Key {settings.VERIAX_API_KEY}"},
timeout=8,
)
response = HttpResponse(answer.content, status=answer.status_code, content_type="application/json")
response["Cache-Control"] = "no-store"
return response
4. Choose an agent and send a message
getAssistants() lists the agents the token can use, with their id, name and description. Let the user pick one, or choose it in your code, then open a conversation with it:
import { VeriaxClient } from '@veriax/sdk'
const client = new VeriaxClient({
baseUrl: 'https://veriax.example.com',
tokenUrl: '/api/veriax-token',
})
const picker = document.querySelector<HTMLSelectElement>('#agent')!
const agents = await client.getAssistants()
picker.replaceChildren(...agents.map((agent) => new Option(agent.name, String(agent.id))))
async function start(agentId: number) {
const thread = await client.createThread({ assistant: agentId })
const chat = client.chat(thread)
const { text } = await chat.send('Hello').result
console.log(text)
}
picker.onchange = () => start(Number(picker.value))
createThread opens a conversation with the agent, chat connects to it and send returns the reply. In the steps below, agentId is the agent you chose.
5. Show the reply as it arrives
Replies stream in. Render the text received so far on every delta, then show the sources the agent cited:
import { renderMarkdown } from '@veriax/sdk/markdown'
const out = document.querySelector<HTMLElement>('#answer')!
const reply = chat.send('What does our returns policy say?', {
onDelta: (_delta, text) => out.replaceChildren(renderMarkdown(text)),
})
const { citations } = await reply.result
chat.stop() ends a reply early; it resolves with stopped: true.
6. Keep the conversation
The SDK remembers the last conversation. On the next load, resume it instead of creating a new one:
const thread = client.lastThread() ?? (await client.createThread({ assistant: agentId }))
const chat = client.chat(thread)
const history = await chat.getMessages()
By default the token and the last conversation live in sessionStorage. See Token and storage for the other options.
7. Going further
- Tools. There is nothing to set up: an agent uses its tools, retrievers and abilities on its own, when the request needs them. If your interface lets the user point the agent at specific tools, as the tools menu of the VERIAX chat does,
client.getTools(agentId)returns that menu andsend(text, { selectedTools })asks the agent to use the chosen ones for that message. - Files.
client.uploadArtifact(chat.threadToken, file)adds a file to the conversation; the agent reads it if it has an ability that reads files.chat.getArtifacts()lists the files, andclient.downloadArtifact(id, chat.threadToken)downloads one, including what the agent produced. - Structured input and output. When an agent declares
input_schema, send the object as JSON text:chat.send(JSON.stringify(value)). When it declaresstructured_output, the reply text is JSON in that shape. Both schemas come withgetAssistants()andcreateThread().
Choose how your application signs in
All three cases use the same SDK code. What changes is the API key and what your server sends to get-token/.
An application account
Use it when the people using your application have no VERIAX user, for example the visitors of a public site. Every token acts as the same account, which reaches only what you share with it.
- In Settings > Users, choose New user and set User type to Application.
- Share with the account the agents it may use, and the knowledge bases those agents read.
- On the account's page, in the Access credentials card, create an API key.
VERIAX refuses to share with an application account an agent that could act with the account's identity, for example through connectors or MCP tools: the sharing window lists what is in the way. Each conversation can be reached only with its own thread token, so visitors never see each other's conversations.
Your own user
Use it for an integration that works on your behalf, such as an internal tool or a script. Create the key in your Profile, as in step 1. Tokens act as you: your agents and your conversations.
Each of your users
Use it when your application signs its users in, for example an ERP or an intranet, and the same people have a VERIAX user with the same email. Each user then works with their own agents and conversations.
- Create an application account and its API key, as above.
- A superuser opens the account's page, turns on Open the chat as any user in the Access credentials card and saves the page.
- Your server adds the email of the user it signed in:
@require_POST
@login_required
def veriax_token(request):
answer = requests.post(
f"{settings.VERIAX_URL}/api/v1/ai/get-token/",
headers={"Authorization": f"Api-Key {settings.VERIAX_API_KEY}"},
json={"email": request.user.email},
timeout=8,
)
response = HttpResponse(answer.content, status=answer.status_code, content_type="application/json")
response["Cache-Control"] = "no-store"
return response
Take the email from your own session, never from the request. VERIAX compares it ignoring case; an email that matches no active user answers 404 unknown_user. A user's profile may differ from user: read it with client.getProfiles() and pass it in the profile option.
Native apps
The SDK runs wherever fetch, WebSocket and AbortSignal.timeout exist, including React Native and WebViews. Two things differ from the browser:
- Storage. Without
sessionStoragethe SDK keeps the token in memory. To keep the conversation across restarts, pass a synchronous storage object (getItem,setItem,removeItem) in thestorageoption. - Your token endpoint. An app has no site cookies: authenticate the call with your own login, for example with the
headersoption oftokenUrl.
import { tokenUrl } from '@veriax/sdk/helpers'
const client = new VeriaxClient({
baseUrl: 'https://veriax.example.com',
getToken: tokenUrl('https://api.example.com/veriax-token', {
headers: () => ({ Authorization: `Bearer ${session.accessToken}` }),
}),
})
Never ship the API key inside the app: an installed app can be decompiled.
Errors and reconnection
The SDK recovers on its own from most problems:
- Network drops. The chat reconnects with an increasing delay. If a reply was cut off, the SDK reads the conversation again: a reply that finished meanwhile arrives with
resynced: true, and a question that never arrived is sent again. - Expired tokens. At 80% of a token's lifetime, and after any
401, the SDK asks your endpoint for a new token and goes on.
Everything else arrives as a VeriaxError with a code; see Error codes. Reply errors reject reply.result, connection errors arrive on chat.on('error'), and REST errors reject the promise of the method you called.
API reference
Entry points
| Import | Contents |
|---|---|
@veriax/sdk | VeriaxClient, ChatSession, Reply, VeriaxError and the types (Thread, Assistant, Citation, TokenResponse, ...) |
@veriax/sdk/helpers | tokenUrl, handleError and the TokenUrlOptions type |
@veriax/sdk/markdown | renderMarkdown |
VeriaxClient
new VeriaxClient(options):
| Option | Default | Notes |
|---|---|---|
baseUrl | required | The VERIAX instance. Must be https://; http:// only towards localhost |
tokenUrl or getToken | one is required | tokenUrl: your token endpoint, called with POST. getToken: your own function returning {token, expires_at, expires_in} |
profile | user | The VERIAX profile in the API paths |
storage | 'session' | Where to keep the token and the last conversation: see Token and storage |
key | default | Storage key suffix, to run several clients on the same page |
fetch, WebSocket | globals | Alternative implementations |
timeoutMs | 10 s | Timeout of the calls to VERIAX |
| Method | What it does |
|---|---|
getAssistants() | The agents the token can open a conversation on, with input_schema and structured_output |
createThread({ assistant, name? }) | Opens a conversation. Returns { id, token, name, assistant, input_schema, structured_output, date_create, date_last_modify } |
getThread(threadToken) | The conversation of that thread token |
revokeThread(threadToken) | Closes the conversation for every token |
chat(thread, options?) | Connects to the conversation and returns its ChatSession |
lastThread() | { token } of the last conversation opened with chat(), if any |
getTools(assistantId) | The agent's tool menu: { tools: [{ name, display_name, icon, priority, visible }], total_count } |
getProfiles() | The profiles of the token's user: [{ value, label }] |
uploadArtifact(threadToken, file, filename?) | Adds a file to the conversation |
downloadArtifact(id, threadToken), downloadPreview(id, threadToken) | A file, or its preview, as a Blob |
tokens.clear() | Forgets the token and the last conversation, for example on logout |
dispose() | Stops the token timer and the visibility listener |
ChatSession
Options of chat(): backoffBaseMs (1 s), backoffMaxMs (30 s), maxReconnectAttempts (10, Infinity for no limit), WebSocket.
| Member | What it does |
|---|---|
send(content, { onDelta?, selectedTools? }) | Sends a message and returns a Reply. A second send during a reply throws busy |
stop() | Stops the reply in progress |
getMessages() | The conversation history: human and ai messages with their citations |
getArtifacts() | The conversation's files |
connect() | Opens the connection; send does it by itself |
close() | Closes the connection and stops reconnecting |
state | idle, connecting, open, reconnecting or closed |
replying | Whether a reply sent by this session is in progress |
threadToken | The conversation's thread token |
on(event, fn) | Adds a listener and returns the function that removes it |
| Event | When it fires |
|---|---|
ready | The connection is ready, after every connection and reconnection. { threadToken, replying } |
state | The connection state changes |
sources | A reply cited sources: { threadToken, citations } |
human-message | A message was sent to the same conversation from elsewhere, for example another tab |
stream-finished | A reply not sent by this session finished |
frame | Every message received from the server |
error | A connection error |
Reply
| Member | What it holds |
|---|---|
for await (const delta of reply) | The text deltas, as they arrive |
reply.text | The text received so far |
reply.result | Promise of { text, stopped, resynced, messageId, citations? } |
Token and storage
storage | Effect |
|---|---|
'session' | sessionStorage: survives a reload of the tab |
'local' | localStorage: shared between tabs and kept after closing them |
'memory' | Nothing is kept: every load gets a new token and starts a new conversation |
an object { getItem, setItem, removeItem } | Your own synchronous storage |
The storage key is veriax.session.<key>. If storage is unavailable, the SDK works in memory. With 'local', a tokens.clear() in one tab signs every tab out.
renderMarkdown
renderMarkdown(text, doc?) returns a DocumentFragment built with DOM APIs only, so text from the model is never parsed as HTML. In Node, pass a document as the second argument. It supports paragraphs, bold, italic, inline code, code blocks, one level of bulleted and numbered lists, and http:, https: and mailto: links, opened in a new tab. Headings, tables, images and raw HTML are shown as text.
Error codes
Every error is a VeriaxError with code, recoverable, message (technical, in English, not for users), and when present status, serverCode, closeCode and serverMessage (text meant for users, such as a usage-limit message).
code | When | Recoverable |
|---|---|---|
session_unavailable | Your endpoint gives no valid token, or VERIAX refuses the token twice | no |
thread_closed | The conversation was closed with revokeThread | no |
assistant_not_allowed | The token cannot use that agent | no |
not_found | Unknown conversation or file | no |
forbidden | Action not allowed | no |
invalid_input | Empty or too long message (4000 characters by default), file too large (10 MiB by default), malformed request | in chat yes, on REST no |
handshake_refused | The connection to the conversation was refused twice | yes |
limit_reached | A usage limit of the agent was reached; see serverMessage | no |
rate_limited | Too many requests, for example more than 10 messages per minute in one conversation | yes |
unavailable | The VERIAX licence is not active | no |
connection_lost | Network, timeout or server error, after the reconnection attempts | yes |
model_error | Error of the model or of the agent | yes |
action_not_allowed | The server refused a socket action | no |
busy | send while a reply is in progress | no |
closed | close() was called, or tokens.clear() during a token request | depends |
Socket close codes
| Close | What the SDK does |
|---|---|
| Handshake refused | Gets a new token and tries again; a second refusal gives handshake_refused |
4401 (token expired, conversation closed, key revoked) | Reconnects at once with the current token; if refused again, thread_closed |
1000 after a limit or error message | Fatal: limit_reached or the error received |
Other codes, or no ready within 15 s | Reconnects with a delay of 1, 2, 4… up to 30 s, at most maxReconnectAttempts times, then connection_lost |
API keys and security
- Keep the key on the server. Never in browser code, an app, a repository, URLs or logs.
- One key per integration, with an expiry. You can then revoke one without stopping the others. Revoking a key ends its tokens at once.
- Protect your token endpoint. Whoever reaches it gets a token. Serve it over HTTPS, require your own login where your application has one, limit requests per user or per IP, and do not open it to other origins with CORS.
- Treat the thread token as a secret. It gives access to the conversation: keep it out of URLs, logs and analytics.
- Display replies safely. Use
renderMarkdownor plain text, neverinnerHTML, and do not embed files from a conversation in the page: they come from users or from the model. - Tell your users. Conversations and files are stored in VERIAX under the account the token acts as.
Requirements on the VERIAX instance
For whoever runs the instance:
- API keys switched on (
THX_AI_API_KEYS_ENABLED) and chat tokens switched on (THX_AI_CHAT_SESSIONS_ENABLED, on by default). - The authentication classes
ApiKeyAuthentication, thenChatSessionAuthentication, beforeSessionAuthentication, andApiKeyOrSessionAuthStackfor the WebSockets. django-cors-headersinstalled, withoutCORS_ALLOW_CREDENTIALS: the SDK never sends cookies.
Version and licence
This page documents @veriax/sdk 0.0.5. The SDK is released under the MIT licence.
FAQ
Does the API key ever reach the browser or the app?
No. The key stays on your server, which uses it to ask VERIAX for a short-lived token and passes only the token on. The SDK works with the token and never sees the key.
Does the SDK draw an interface?
No. It gives you data and events, and you build the interface. To display replies there is renderMarkdown, which builds DOM nodes without using innerHTML.
Does it work in React Native?
Yes. The SDK needs fetch, WebSocket and AbortSignal.timeout. Without sessionStorage it keeps the token in memory; pass a synchronous storage object to keep it across restarts.
How long does a token last?
30 minutes by default. Before it expires the SDK asks your server for a new one, and conversations stay reachable with every new token.