arcaveli-client
v1.0.0
Published
Official client for the Arcaveli zero-knowledge AI proxy. Encrypted chat, agents, files, and canvas with end-to-end RSA-OAEP + AES-256-GCM. Works in Node 18+ and modern browsers.
Maintainers
Readme
arcaveli-client
Official client for the Arcaveli zero-knowledge AI proxy.
Every response is encrypted server-side with your RSA-2048 public key (RSA-OAEP-SHA256 wrapping AES-256-GCM). The matching private key — held only on your machine — is required to decrypt. This client handles the unwrap automatically.
A breach of Arcaveli's database produces ciphertext only. We cannot read your conversations, file uploads, or canvas documents. By design.
- Zero dependencies. Just Node 18+ (built-in
fetch+crypto) or any modern browser (Web Crypto). - Single file. ~18 KB unminified, no build step.
- TypeScript declarations included.
- Drop-in OpenAI-compatible
/v1/chat/completionsshape — but the response body is encrypted (use this client to auto-decrypt).
npm install arcaveli-clientQuick start
Three steps: generate an API key from your dashboard, run onboarding once, then call chat().
1. Generate an API key
Open arcaveli.com/dashboard → Generate API key (in the API Access card). Copy the key — it's shown once. Looks like arc_4a402a2adc7a3724a486d4802d10aa3c80d4cb2f1c42b89c0dce1c6311bd3e66.
2. Onboard (one-time per key)
const { ArcaveliClient } = require('arcaveli-client');
const fs = require('fs');
const { privateKey } = await ArcaveliClient.onboard({
apiKey: 'arc_xxxxx',
});
// Save the private key NOW — the server does not store it.
// If you lose it, every encrypted message tied to this key becomes
// permanently unreadable.
fs.writeFileSync('arcaveli.pem', privateKey);3. Use forever
const { ArcaveliClient } = require('arcaveli-client');
const fs = require('fs');
const client = new ArcaveliClient({
apiKey: process.env.ARCAVELI_API_KEY,
privateKey: fs.readFileSync('arcaveli.pem', 'utf8'),
});
const r = await client.chat([
{ role: 'user', content: 'In one sentence, what is zero-knowledge encryption?' },
]);
console.log(r.content);
// → "Zero-knowledge encryption is a system where one party can prove..."
console.log(r.usage);
// → { prompt_tokens: 14, completion_tokens: 33, total_tokens: 47, ... }That's it. No keypair generation, no decrypt boilerplate — r.content is plaintext.
Recipes
Multi-turn conversation
Pass conversation_id from the first call to give Claude memory across turns.
const r1 = await client.chat([{ role: 'user', content: 'My client is Acme Corp.' }]);
const r2 = await client.chat(
[
{ role: 'user', content: 'My client is Acme Corp.' },
{ role: 'assistant', content: r1.content },
{ role: 'user', content: 'Draft an NDA for them.' },
],
{ conversation_id: r1.conversationId }
);
// r2.content references "Acme Corp" without you repeating it.PDF Q&A
Upload a PDF — the server extracts text once, encrypts it, returns the plaintext one time so you can inject it as a system prompt.
const file = await client.uploadFile(
fs.readFileSync('./contract.pdf'),
'contract.pdf',
'application/pdf',
);
const r = await client.chat([
{ role: 'system', content: `Document content:\n\n${file.extracted_text}` },
{ role: 'user', content: 'List every payment obligation, with section numbers.' },
]);
console.log(r.content);Agent research with cited sources
The case-research and medical-literature agents run a server-side tool loop (CourtListener / PubMed), then return the synthesized answer plus the sources cited.
const r = await client.chat(
[{ role: 'user', content: '2019 9th Circuit cases on FTC consent decrees.' }],
{ agent: 'case-research' }
);
console.log(r.content);
for (const tool of r.toolsUsed || []) {
for (const src of tool.sources || []) {
console.log(`- ${src.label} (${src.meta})`);
console.log(` ${src.url}`);
}
}Streaming with progress events
Streaming returns a single encrypted envelope at the end (RSA-OAEP wraps an AES key per response — there's no incremental ciphertext). The value is the progress stream during agent loops.
await client.chatStream(
[{ role: 'user', content: 'Find recent appellate rulings on...' }],
{ agent: 'case-research' },
(e) => {
if (e.progress?.phase === 'tool_use') {
console.log('Calling tools:', e.progress.tools.join(', '));
}
if (e.text) console.log('Final answer:', e.text);
},
);API reference
class ArcaveliClient {
constructor(opts: { apiKey: string; privateKey: string; baseUrl?: string });
static onboard(opts: { apiKey: string; baseUrl?: string }): Promise<{
onboarded: true;
publicKey: string;
privateKey: string; // store immediately — not retrievable later
warning: string;
nextSteps: { storeKey: string; docs: string };
}>;
// Chat
chat(messages, options?): Promise<ChatResult>;
chatStream(messages, options, onChunk): Promise<ChatResult>;
// Models / agents
listModels(): Promise<ListResponse<ModelInfo>>;
listAgents(): Promise<ListResponse<AgentInfo>>;
// Conversations (encrypted history persisted server-side)
listConversations(): Promise<ListResponse<ConversationSummary>>;
getConversation(id): Promise<ConversationDetail>; // messages decrypted inline
createConversation(): Promise<{ id, created_at, updated_at }>;
deleteConversation(id): Promise<{ ok: true }>;
// Files (PDF / DOCX / TXT, ≤10 MB)
uploadFile(buffer, filename, mimeType?): Promise<FileUploadResponse>;
listFiles(): Promise<ListResponse<FileSummary>>; // filenames decrypted
deleteFile(id): Promise<{ ok: true }>;
// Canvas (encrypted Quill Delta documents)
createDocument(title, delta): Promise<CanvasDocSummary>;
listDocuments(): Promise<ListResponse<CanvasDocSummary>>; // titles decrypted
getDocument(id): Promise<CanvasDocDetail>; // title + delta decrypted
updateDocument(id, body): Promise<CanvasDocSummary>;
deleteDocument(id): Promise<{ ok: true }>;
exportDocument(id, delta, title?): Promise<Buffer | Blob>; // DOCX binary
}Full TypeScript declarations ship with the package (index.d.ts).
Errors
Every error response is JSON of shape { error: string, code?: string, ... }. The client throws an Error with status (HTTP code) and body (the JSON payload) attached, so:
try {
await client.chat([{ role: 'user', content: 'hi' }]);
} catch (err) {
console.error(err.status); // e.g. 429
console.error(err.body.code); // e.g. 'credits_exhausted'
console.error(err.message); // e.g. 'Monthly AI credits exhausted'
}Common codes:
| Status | code | Meaning |
|---|---|---|
| 401 | — | Invalid or missing API key |
| 402 | — | Active subscription required |
| 402 | byok_key_missing | Solo plan, but no provider key on file |
| 403 | — | Onboarding required (call ArcaveliClient.onboard()) |
| 403 | managed_anthropic_only | Starter/Business — must pick a claude-* model |
| 404 | — | Resource not visible to your API surface |
| 409 | — | Already onboarded with this key |
| 413 | — | Payload too large (10 MB / 4 MB) |
| 429 | — | Rate limit hit (60 req/min/user) |
| 429 | credits_exhausted | Monthly credits used up + no top-up balance |
| 502 | — | Upstream provider error — safe to retry |
Every successful response includes X-Rate-Limit-Limit + X-Rate-Limit-Remaining headers so well-behaved clients can pace themselves.
OpenAI compatibility
The chat shape mirrors OpenAI so existing OpenAI client code can point at the Arcaveli base URL — but the response body is encrypted, so the official openai SDK will display ciphertext. Use this client to auto-decrypt:
// Drop-in compatibility (but you'll see ciphertext)
const openai = new OpenAI({
apiKey: 'arc_xxxxx',
baseURL: 'https://arcaveli.com/api/v1',
});Security model
- Server holds the public key. The matching private key is generated server-side at onboarding, returned once, and never persisted.
- Encryption at rest. Every message body, file upload extraction, and canvas document is encrypted with a fresh AES-256-GCM key, then wrapped with your RSA public key (RSA-OAEP-SHA256). The opaque envelope is what touches the database.
- API keys are SHA-256 hashed. The plaintext is never stored.
- No silent recovery. Lose your private key → encrypted history is permanently unreadable. By design.
Read the full security overview at arcaveli.com/security.
Plans + rate limits
- Solo ($49/mo) — bring your own provider keys (Anthropic / OpenAI / Google / Groq / OpenRouter)
- Starter ($149/mo) — 5,000,000 AI credits/month on managed Anthropic
- Business ($499/mo) — 25,000,000 credits/month, shared across team workspace
- Top-up packs — never expire, charged at $50/M (Solo), $30/M (Starter), $20/M (Business)
API calls count against the same monthly allotment. 60 requests/min/user rate limit.
Links
- Documentation
- Dashboard — generate / revoke API keys
- Security overview
- Pricing
- Support
License
MIT — see LICENSE.
