Integration guide β
Voxt exposes typed services through ActualLab RPC. The app uses this same service layer for chats, accounts, media, notifications, and live sessions. A service method has typed arguments and a typed asynchronous result. It is not an HTTP REST route or a JSON-RPC method.
For an AI assistant that needs to list chats and read or post messages, start with MCP. Use RPC when you need the wider service surface or are building a client with the matching .NET contracts and ActualLab RPC stack.
Install the NuGet package β
Reference ActualChat.Api.Contracts, which contains the service interfaces and command contracts. It brings in ActualChat.Api for the product data models through its dependency.
For the source revision documented here (the 2.22-alpha series; commit d2eee636e0):
dotnet add package ActualChat.Api.Contracts --version 2.22.569-alpha-gd2eee636e0The version number follows the product's Nerdbank.GitVersioning rule: the series from version.json, the commit height since that series began, and the abbreviated commit hash. It was derived from the repository history, not read from the NuGet feed, so confirm that the package exists before pinning it. Choose a package version compatible with the server you connect to. The version above matches this reference's source commit; it is not a promise that it is the latest release. Development builds can require a prerelease package.
The package retains the historical ActualChat name. Installing it supplies the contracts; you still need to configure the ActualLab RPC client and authentication described below.
Connect and authenticate β
The WebSocket RPC endpoint is /rpc/ws on the deployment you are using, for example wss://voxt.ai/rpc/ws. Use a compatible ActualLab RPC client and the Voxt contract assemblies from the product source revision you target. The C# examples below assume that client setup has already supplied the service proxies; they are not standalone connection programs.
- Sign in to Voxt and open Settings β API Keys.
- Create a named key for the integration and copy the value.
- Supply that value as the
SessionHTTP header when opening a non-browser RPC connection. Construct the matchingSessionargument for your calls. - Check your account with
IAccounts.GetOwnbefore performing writes.

The connection resolves authentication in this order: Session header, protected session query token, then browser session cookie. The query form expects a protected token, so do not substitute a raw API key there. ISecureTokens.CreateForSession creates a short-lived protected token. A browser cookie is for an interactive session; API-key and OAuth sessions are explicitly rejected when presented through that cookie. OAuth sessions are a third session kind (SessionKind.OAuth) created when a user approves a client.
An API key represents its account. It does not bypass chat membership, roles, or operation-specific checks. Create separate keys for integrations and deactivate keys you no longer use. The account API can list API-key sessions and deactivate a session by its returned IdPrefix.
Authentication resolution, connection binding, and account operations.
Find a chat β
Use IContacts.ListPlaceIds to discover the caller's places and IContacts.ListIds to list contact entries in a scope. A contact can point to a person, a group chat, or a place. Resolve the contact with IContacts.Get, then use its chat identifier with IChats.Get. Use IPlaces for place details and IAuthors for chat participants.
An ID identifies an object; it does not authorize access to it. Resolve permissions with IChats.GetRules or IPlaces.GetRules before presenting actions in a client.
Identifiers β
Treat returned IDs as opaque values unless you are using the product's typed factories and parsers. Preserve their complete string form.
| Type | What it identifies |
|---|---|
UserId | An account across Voxt. |
ChatId | A group, peer, place chat, or thread. It is the usual scope for message operations. |
GroupChatId | A group chat outside the place-specific identifier form. |
PeerChatId | A one-to-one chat between two account IDs. The product factory orders the two IDs consistently. |
PlaceId | A place containing chats and a shared membership. |
PlaceChatId | A chat within a place. |
ThreadChatId | A thread under a parent chat; threads can have parents of different chat kinds. |
ChatEntryId | A message or system entry, including its chat and local entry number. The current factory emits chatId:0:localId; parsers also accept the legacy kind component. |
LocalId / EntryLid | A numeric entry identifier within one chat. It is not globally unique. |
AuthorId | A participant identity within a chat. It differs from the account's UserId. |
ContactId | The caller-owned relationship to a chat; its canonical representation includes owner and chat separated by a space. |
RoleId | A role within a chat. |
MediaId / UploadId | A media object and a resumable upload respectively. They are different stages of the media workflow. |
ConversationId | A conversation within a chat, used for conversation summaries and live-session data. |
AliasId | A friendly alias resolved through IAliases; it is not interchangeable with the target object's ID. |
The identifier reference covers the remaining identifier types and links to their parsers. Concrete GroupChatId and PeerChatId are useful client factories even though the methods generally expose the ChatId base type.
Read messages β
IChats.GetIdRange returns a half-open range [Start, End). End is one beyond the last entry ID. Gaps and removed entries are possible: subtracting the endpoints does not give a reliable message count.
Use IChats.GetTile with a supported local-ID tile range to retrieve entries. The matching product client exposes tile helpers; reuse them rather than assuming any arbitrary range is a supported tile. GetTileNonComputed is a plain-call alternative for scanning. ChatTile.Entries contains polymorphic ChatEntry values, including text and system events.
For media browsing, GetContentPeriods provides period keys and pagination, followed by GetVisualMediaPeriod, GetFilePeriod, or GetLinkPeriod. Keep the returned period and continuation values rather than deriving them from display dates.
Post and edit a message β
With an authenticated session, a resolved chatId, and an IChats proxy named chats:
var command = new Chats_UpsertEntry {
Session = session,
ChatId = chatId,
LocalId = null,
Text = "The project notes are ready for review.",
};
var entry = await chats.OnUpsertEntry(command, cancellationToken);Use the returned entry's local ID to edit it. The server checks whether the caller can write or edit that entry. RepliedEntryLid carries an optional reply target. Attachments use ChatEntryAttachment records; setting text that looks like a file path does not upload a file.
Most command records inherit ApiCommand<TResult>. That base adds Session and a generated Uuid. Retain the command object and its UUID when retrying the same logical write; constructing a new command means a new operation. Do not assume every method has command deduplication: ordinary methods and commands marked with INotDeduplicated (high-frequency operations, and ones that keep their own durable receipt) have different semantics.
A message can also be posted as it is produced. IChats.StreamEntry takes an RpcStream<string> of text chunks; StreamVoice takes an RpcStream<VoiceStreamPart> carrying text, Ogg Opus audio, or both. A caller that cannot hold a stream open uses the same operations call by call: StartEntryStream, AppendEntryStream (with the offset the server expects), and FinishEntryStream for text, and StartVoiceStream, AppendVoiceStream, and FinishVoiceStream for voice. Each append reports where the server is, so a lost or repeated call can resume. Chats_UpsertEntry.QuotedText carries the quoted part of a reply.
For deletion, restoration, forwarding, and pinning, use the named IChats command methods. A successful request remains subject to the caller's permissions; the service registration is not a permission list.
Changes and versions β
Change<T> uses the same payload type for create and update. Change<TCreate, TUpdate> permits different payload types. Select exactly one of Create, Update, or Remove. Diff records contain the fields that can change.
Option<T> distinguishes an omitted update from a supplied value. In particular, Option<T?> can mean βleave it alone,β βset it to null,β or βset this value.β Do not flatten those three states into one nullable value.
Where a command accepts ExpectedVersion, pass the version of the record you read when you need optimistic concurrency checking. If it conflicts, fetch current state and reconcile the intended change. Product record versions are separate from the documentation's source commit and from application release versions.
Media and live streams β
The media workflow separates reserving a media ID, creating an upload, appending bytes at an offset, processing the upload, and attaching the resulting media. IUploads.GetOffset supports resume; MediaProgress reports processing stage and errors. Use the media services and media types together.
RPC uploads check the authenticated account and ownership of the upload. Uploads_Create.Length is nullable in the contract, but the implementation currently rejects a null length; supply the known byte length. File and chunk limits and rate limits also apply. HTTP upload routes and MCP tools are separate surfaces, so their authentication and capabilities must not be inferred from these RPC methods. This guide has not exercised a live API-key upload.
Audio and video streaming use RpcStream<T> and encoded frames with timing and format metadata. They require matching codec and stream lifecycle handling. A local audio or video file cannot be passed directly as a live frame stream. See streaming services.
Errors and recovery β
RPC failures surface through the client as call errors or exceptions; they are not a documented table of REST status codes. Handle these cases separately:
- Authentication or expired/deactivated session: obtain a valid key or sign in again.
- Permission or membership failure: inspect the account and chat/place rules; retries do not grant access.
- Missing object: refresh the list or stop referring to the removed object. Some reads return a nullable result rather than throwing.
- Invalid input or constraint: correct IDs, ranges, required fields, or the selected change operation.
- Version conflict: reread and reconcile before submitting the update again.
- Transport interruption: let the RPC client's reconnect behavior run; retry writes with their original command identity where supported.
Avoid copying exception strings into program logic. Server messages may change or be localized. Use the client's structured error information where available.
Compatibility β
ISystemProperties.GetServerApiInfo and GetServerApiInfoNC return ServerApiInfo, including compatibility and version strings for an expected client version. Align contracts and the RPC runtime with the deployment. Source method names sometimes have LegacyName aliases for older clients; a C# signature alone is not a recipe for manually constructing a wire method name.
Serialized unions such as ChatEntry, Invite and Notification can have multiple concrete shapes. The map lists their declared variants. A client should use the matching serializers and preserve their forward-compatibility behavior instead of assuming all entries are text messages.
Shared types β
These framework and .NET types form the boundary of the product type map:
| Type | Meaning |
|---|---|
Session | Authentication/session identity supplied with API calls and commands. |
Task<T> / Task | An asynchronous result or completion. |
CancellationToken | Client-side call cancellation; not a business payload field. |
Moment | ActualLab time instant; use its serializer rather than inventing a timestamp encoding. |
TimeSpan / DateTime | .NET duration and date/time values, respectively. Units named in individual members still apply. |
Symbol | A string-like identifier/value used by the contract. |
Option<T> | Explicit presence/absence plus a value; important for partial updates. |
Result<T> | An item result that can carry a value or an error, used for per-item bulk outcomes. |
Unit | A result with no business value. |
ApiArray<T>, ApiList<T>, ApiSet<T>, ApiMap<K,V> | Serialization-friendly collections. Preserve list, set, and map semantics. |
ApiNullable8<T> | A serialization-friendly nullable value wrapper. |
T[], IReadOnlyList<T>, ImmutableList<T>, Dictionary<K,V> | Standard collection shapes; the element/key/value types remain part of the contract. |
ReadOnlyMemory<byte> / byte[] | Binary data, not a text file path. |
RpcStream<T> | A sequence carried over the RPC streaming protocol. |
MetadataBag | Extensible metadata associated with an upload or media object. Reuse the product's metadata helpers and serializers. |
RpcPeerConnectionStateKind | ActualLab RPC connection-state enum used in diagnostic records. |
ExceptionInfo | A serializable exception description (type and message), used for per-item errors such as ChatImportEntryResult.Error. |
Marker interfaces shown in type declarations describe implementation capabilities rather than additional serialized payload fields. Generic parameters such as T, TItem, and TResult are replaced by the concrete type at the use site.