AI API

Org-scoped AI provider management and a pass-through proxy: configure provider credentials once per org, then call any provider's native API through Kapable without shipping provider keys to clients.

Token tier: API keys work here

The AI module is scope-gated: an sk_live_ key with the right scope gets 200 (verified live — it's one of the surfaces in the SDK's golden-path smoke test).

Providers

MethodPathSDK (client.ai)
GET/v1/providerslistProviders
GET/v1/providers/{id}getProvider
POST/v1/providers/{id}/configureconfigureProvider
DELETE/v1/providers/{id}/configureremoveProviderConfig

Provider proxy

POST /v1/proxy/{provider}/{path} forwards the request to the configured provider with the org's stored credentials. The SDK returns the raw Response so you keep full control over streaming bodies.

Zero-config AI: OpenRouter only, for now

A freshly provisioned org can call client.ai.proxy('openrouter', …) (or POST /v1/proxy/openrouter/* directly) with no setup — the platform funds a default key for that provider, so the call just works. This default is provider-specific, by design: only providers the platform funds a key for get one (currently just OpenRouter); calling any other provider without first configuring your own key still returns 400 No API key configured for provider. Configuring your own key for a provider (configureProvider) always overrides the platform default for that provider.

SDK Examples

// List providers and their configuration state
const { providers } = await client.ai.listProviders();

// Point the provider at a strongbox secret holding the org's upstream key —
// the raw key never travels through this SDK (store it via client.secrets first)
await client.ai.configureProvider('anthropic', { key_name: 'anthropic-api-key' });

// Call the provider's native API through the proxy
const res = await client.ai.proxy('anthropic', '/v1/messages', {
  body: {
    model: 'claude-sonnet-4-6',
    max_tokens: 256,
    messages: [{ role: 'user', content: 'Hello from Kapable' }],
  },
});
const completion = await res.json();
let providers = client.ai().list_providers(ListProvidersParams::default()).await?;

let payload = serde_json::json!({
    "model": "claude-sonnet-4-6",
    "max_tokens": 256,
    "messages": [{"role": "user", "content": "Hello from Kapable"}]
});
let res = client.ai().proxy("anthropic", "/v1/messages", ProxyRequest {
    body: Some(serde_json::to_vec(&payload)?),
    ..ProxyRequest::post()
}).await?;

Text-to-speech (M10)

A dedicated endpoint for spoken-summary generation — the counterpart to Artifacts' audio summaries. Distinct from the generic provider proxy above: this owns ElevenLabs key resolution so callers never touch the raw upstream API or strongbox directly.

MethodPathSDK (client.ai)
POST/v1/ai/ttstts
GET/v1/ai/admin/tts-settingsgetTtsSettings
PUT/v1/ai/admin/tts-settingsputTtsSettings
DELETE/v1/ai/admin/tts-settingsdeleteTtsSettings

Key resolution: if the org has stored its own ElevenLabs key (PUT /v1/ai/admin/tts-settings, admin/owner only — encrypted at rest, never echoed back), it synthesizes with no metering. Otherwise the platform's own key synthesizes and a usage event (characters synthesized) is recorded. text is capped at ~5000 characters per call (422 if exceeded); voice is optional (org default, else a platform default).

// BYO org key (admin/owner only) — no metering once set.
await client.ai.putTtsSettings({ api_key: process.env.ELEVENLABS_API_KEY });

// Synthesize — returns raw audio/mpeg bytes.
const bytes = await client.ai.tts({ text: "Relate's fifty-fourth tick shipped…" });

// Attach to an artifact version (base64-encode first).
const b64 = Buffer.from(bytes).toString('base64');
await client.artifacts.attachAudio('relate-tick-54', 1, { data: b64 });
let bytes = client.ai().tts(TtsRequest { text: "…".into(), voice: None }).await?;

let b64 = base64::engine::general_purpose::STANDARD.encode(&bytes);
client.artifacts().attach_audio("relate-tick-54", 1, AudioAttach {
    data: b64, content_type: None, duration_secs: None, generated_via: None,
}).await?;