SDK & API Reference
v1.0.0The @atai-group/sdk and the Atai Runtime API
One typed client, one API key, and every capability your application needs — AI, messaging, payments, search, maps and more. Everything a generated (or any) application can do with Atai, documented end to end.
Overview
The Atai Runtime API is the single service boundary between your application and everything Atai operates: AI generation, voice, web search and scraping, email, SMS, WhatsApp, push notifications, maps, scheduling, vector storage, the app database, and checkout. The @atai-group/sdk is its official typed client.
One credential
A project-scoped Atai API key is the only secret your app needs. Provider accounts and provider keys stay inside Atai — always.
One vocabulary
Your app speaks Atai capabilities (ai.text, email, payments…). Atai normalizes every provider response — you never see a provider's wire format.
One meter
Usage is metered and billed in Atai credits per request. No per-provider billing, no per-provider rate limits to juggle.
How the pieces fit together
Your application (@atai-group/sdk)
│ Authorization: Bearer atai_<environment>_<secret>
▼
Atai Runtime API (https://atai.ink/api/runtime/v1)
authenticate → authorize → validate → route
▼
Atai providers & infrastructure (never visible to your app)- Typed to the contract. Every capability method mirrors the Runtime API request/response contract 1:1 — invalid input is caught client-side before it ever hits the network.
- Zero runtime dependencies. Pure fetch + standard JavaScript. Runs in Node.js ≥ 18, serverless, edge runtimes, and browsers.
- Safe by design. The key is never logged or serialized into errors; error objects carry only code, status, message, and a request ID for support.
Building the product behind the code? The plan-first flow lives in Collaborate & Refine Your Plan →
Installation
Install the SDK from npm:
npm install @atai-group/sdkpnpm add @atai-group/sdkyarn add @atai-group/sdkRequirements
- Node.js 18 or newer (or any runtime with a global
fetch) - ES modules (the package ships ESM only)
- TypeScript ≥ 5 for first-class types — JavaScript works fine too
Runtime environments
The SDK runs anywhere standard JavaScript runs:
Node.js
Express, Next.js server routes, workers — anywhere ≥ Node 18.
Serverless & edge
Vercel functions, Cloudflare Workers, Deno Deploy, Bun.
Browsers
Possible but not recommended for keys — see Security Best Practices.
Generated apps
Atai provisions the key and dependency automatically at build time.
API Keys
Every Runtime request is authenticated with an Atai runtime API key — a project-scoped credential that names exactly one project, one environment, and a set of capability scopes.
Key format
atai_<environment>_<secret>
atai_production_a81fK2x9dQ7vN4mZpLw3sE6rT1yU8iO0 ← production key
atai_development_b2cD4eF6gH8jK1lM3nP5qR7s9tU2vW4x ← development keyThe prefix atai_production_ / atai_development_ is part of the key itself and is safe to display — dashboards show it as the key's identity. The remaining secret portion is what authenticates requests and is never stored in plaintext by Atai.
Environments
development
For your dev/test build. Isolated usage records and limits. Create freely while building.
production
For your live application serving real users. Treat its keys with production-grade care.
Creating keys
Keys are managed from your project workspace — Workspace → Runtime → API keys:
- Open your project and go to the Runtime section
- Click Create key, choose the environment
- Optionally restrict scopes (leave empty to grant all capabilities the project is entitled to)
- Optionally set an expiry date
- Copy the secret immediately — it is shown exactly once and can never be retrieved again
Generated applications don't need this step: Atai provisions a scoped key automatically and injects it into the app's secret store (see Auto-Provisioning).
Create, rotate and monitor keys, watch usage & health, and test every endpoint against your real key in the Developer Portal →
Scopes
A scope equals a capability ID. An empty scope list grants every capability the project's plan and entitlement allow; a non-empty list grants exactly those capabilities. Requests outside a key's scopes are rejected with 403 runtime_capability_not_allowed.
["ai.text", "email", "payments"] ← only these three capabilities
[] ← all entitled capabilitiesRotation & revocation
- Rotate replaces a key atomically: a new secret is issued (shown once), the old secret stops working immediately. Lineage is preserved on both keys.
- Revoke permanently disables a key. History is preserved; revoked keys can never authenticate again.
- Expiry — keys may carry a future expiry timestamp; expired keys are rejected like revoked ones (
401 runtime_key_expired).
Treat your key like a password
Never commit it to Git, never ship it to the browser, never paste it into client-side code. If a key leaks, rotate it from the dashboard — the leaked secret stops working instantly.
Quickstart
Initialize the client once per credential and call a capability:
import { Atai } from "@atai-group/sdk"
const atai = new Atai({
apiKey: process.env.ATAI_API_KEY!, // atai_<environment>_<secret>
// baseUrl is optional — defaults to Atai's production runtime origin.
})const result = await atai.ai.chat({
messages: [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "Write a haiku about databases." },
],
temperature: 0.7,
max_tokens: 200,
})
console.log(result.content) // the assistant's reply
console.log(result.model) // the model Atai actually used
console.log(result.usage?.totalTokens) // token usage for this callconst health = await atai.health.check()
console.log(health.status) // "operational"
console.log(health.identity.projectId) // your project, from the key record
console.log(health.identity.environment) // "development" | "production"One client per credential
The client is an instance model: create one per key/environment. Configuration is validated at construction — requests can never change the credential or the origin afterward. Concurrent calls are safe and fully isolated.
const dev = new Atai({ apiKey: process.env.ATAI_DEV_KEY! })
const prod = new Atai({ apiKey: process.env.ATAI_PROD_KEY! })Cancellation & timeouts
Pass a standard AbortSignal. A default 60-second per-request timeout applies. Cancellations and timeouts are always reported as distinct atai_aborted / atai_timeout errors — never masked as network or provider failures.
const controller = new AbortController()
setTimeout(() => controller.abort(), 5000)
try {
await atai.ai.chat({ messages: [{ role: "user", content: "Long task?" }] }, { signal: controller.signal })
} catch (e) {
// e.code === "atai_aborted" when cancelled by the caller
}Capabilities
Each capability is a namespace on the client. Under the hood every call is the same Runtime request — { capability, operation, input } — so the SDK's types always mirror the wire contract.
AI
atai.aiText generation, completion, and embeddings. The workhorse for assistants, content, and understanding.
chatcompletionembedImage generation
atai.imageText-to-image (and image-to-image with reference photos) — product shots, illustrations, and mockups.
generateImageVideo generation
atai.videoText-to-video as an async job — submit a prompt, poll for the finished clip.
generateVideoSpeech & transcription
atai.speech · atai.transcribeText-to-speech with built-in voices, and audio-to-text across languages and formats.
speaktranscribeVision
atai.visionMultimodal understanding — send images, PDFs, audio or video alongside text for analysis.
visionInputVoice
atai.voiceTurn text into natural speech audio (returned base64-encoded with its MIME type).
synthesizeSearch
atai.searchQuery the open web and get ranked, described results.
webWeb
atai.webScrape a public URL into clean markdown, with links and truncation info.
scrapeatai.emailTransactional email — receipts, resets, digests, notifications.
sendSMS & WhatsApp
atai.sms · atai.whatsappText messages and WhatsApp messaging to phone numbers.
sendNotifications
atai.notificationsPush notifications to a user's device via its platform token.
sendMaps
atai.mapsAddresses to coordinates and back, with optional country and language hints.
geocodereverseGeocodeCalendar
atai.calendarList upcoming bookings and create new ones with attendees.
listBookingscreateBookingVectors
atai.vectorsStore embeddings and search them — semantic search, recommendations, RAG.
upsertsearchdeleteDatabase
atai.dbThe app's runtime database: filter, sort, paginate, and mutate records.
querycreateeditdeletePayments
atai.paymentsCreate checkout sessions for one-time purchases and order flows.
createCheckoutai — text generation
const result = await atai.ai.chat({
model: "openai/gpt-5.2", // optional — Atai's default model otherwise
messages: [
{ role: "system", content: "You are concise." },
{ role: "user", content: "Summarize this order." },
],
max_tokens: 300, // 1–1,000,000
temperature: 0.4, // 0–2
stop: ["###"], // string or up to 4 strings
seed: 42,
})
// result: { id, model, content, finishReason, usage? }| Parameter | Type | Description |
|---|---|---|
messagesrequired | AiMessage[] | 1–128 messages; each content is 1–100,000 characters. Roles: user, assistant, system. |
modeloptional | string | Caller-selected model identifier (max 200 chars). Omitted = Atai's configured default. |
temperatureoptional | number | 0–2. Sampling randomness. |
top_poptional | number | (0, 1]. Nucleus sampling. |
max_tokensoptional | number | 1–1,000,000. Maximum tokens to generate. |
stopoptional | string | string[] | Up to 4 stop sequences, each max 100 chars. |
frequency_penaltyoptional | number | -2 to 2. |
presence_penaltyoptional | number | -2 to 2. |
seedoptional | number | Integer seed for (best-effort) reproducibility. |
email — send email
await atai.email.send({
to: ["customer@example.com"],
subject: "Your receipt",
text: "Thanks for your purchase!",
html: "<p>Thanks for your purchase!</p>",
replyTo: "support@yourapp.com",
})
// → { id }sms / whatsapp — send messages
await atai.sms.send({ to: "+15551234567", body: "Your code is 8291" })
await atai.whatsapp.send({ to: "+15551234567", body: "Order confirmed 🎉" })
// → { messageId, status, segments? }db — the app database
const { records, total } = await atai.db.query({
tableName: "orders",
filter: { status: "paid" },
sortBy: "createdAt",
sortDirection: "desc",
limit: 20,
skip: 0,
})
const created = await atai.db.create({ tableName: "orders", data: { item: "Latte", qty: 2 } })
const updated = await atai.db.edit({ tableName: "orders", recordId: created.id!, data: { status: "paid" } })
await atai.db.delete({ tableName: "orders", recordId: created.id! })payments — checkout
const checkout = await atai.payments.createCheckout({
items: [{ name: "Pro plan", quantity: 1, price: 19.99 }],
customerEmail: "customer@example.com",
successUrl: "https://yourapp.com/success",
cancelUrl: "https://yourapp.com/pricing",
idempotencyKey: "order-8291", // deduplicates retries
currency: "USD",
})
// → { checkoutId, checkoutUrl, status } — redirect the customer to checkoutUrlMultimodal — images, video, speech, transcription, vision
// Image generation (text-to-image, or image-to-image via input_references)
const image = await atai.image.generateImage({
prompt: "A red panda astronaut floating in space",
model: "google/gemini-2.5-flash-image", // OpenRouter model id
aspect_ratio: "16:9",
})
// → { images: [{ b64, mediaType }] } — base64-encoded image bytes
// Video generation (async job — submit, then poll with { jobId })
const job = await atai.video.generateVideo({
prompt: "A timelapse of a city skyline at dusk",
model: "google/veo-3",
duration: 5,
})
// → { jobId, status: "submitted" } — poll: generateVideo({ jobId: job.jobId })
// Text-to-speech (returns base64 audio)
const speech = await atai.speech.speak({
input: "Hello from Atai!",
model: "openai/gpt-4o-mini-tts-2025-12-15",
voice: "alloy",
response_format: "mp3",
})
// → { audioBase64, mimeType }
// Transcription (speech-to-text; base64 audio in)
const text = await atai.transcribe.transcribe({
input_audio: { data: audioBase64, format: "wav" },
model: "openai/whisper-large-v3",
language: "en", // optional hint
})
// → { text }
// Vision — multimodal understanding (images, PDFs, audio, video + text)
const answer = await atai.vision.visionInput({
messages: [{
role: "user",
content: [
{ type: "text", text: "What is in this image?" },
{ type: "image_url", image_url: { url: "https://example.com/photo.jpg" } },
// Also supported: { type: "file", file: { file_data } } for PDFs,
// { type: "input_audio", input_audio: { data, format } },
// { type: "video_url", video_url: { url } }
],
}],
})
// → { content, model? }Everything else
// Voice — text to speech
const audio = await atai.voice.synthesize({ text: "Hello!", voiceId: "optional" })
// → { audioBase64, mimeType, voiceId, modelId, characters }
// Web search
const found = await atai.search.web({ query: "best coffee in Kampala", limit: 5 })
// → { results: [{ title, url, description? }], creditsUsed? }
// Scrape a public URL
const page = await atai.web.scrape({ url: "https://example.com" })
// → { url, title?, markdown?, links?, truncated?, creditsUsed? }
// Push notification
await atai.notifications.send({ token: deviceToken, title: "New order", body: "Table 4 ordered." })
// Maps
const places = await atai.maps.geocode({ query: "Kampala", limit: 5 })
const rev = await atai.maps.reverseGeocode({ longitude: 32.58, latitude: 0.34 })
// Calendar
const bookings = await atai.calendar.listBookings({ startDate: "2026-10-01", limit: 10 })
await atai.calendar.createBooking({ title: "Consultation", startTime: "2026-10-02T09:00:00Z", endTime: "2026-10-02T10:00:00Z" })
// Vectors — semantic search
await atai.vectors.upsert({ vectors: [{ id: "doc-1", values: embedding, metadata: { page: 1 } }] })
const hits = await atai.vectors.search({ vector: embedding, topK: 5 })
await atai.vectors.delete({ ids: ["doc-1"] })REST API Reference
The SDK wraps the plain HTTP Runtime API. You can use the REST surface directly (from any language) — the contract below is identical to what the SDK sends.
Base URL & auth
https://atai.ink/api/runtime/v1Authorization: Bearer atai_production_xxxxxxxxxxxxxxxx/api/runtime/v1· Bearer keyInvoke any capability. This is the single invocation endpoint.
curl -X POST https://atai.ink/api/runtime/v1 \
-H "Authorization: Bearer $ATAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"capability": "ai.text",
"operation": "chat",
"input": {
"messages": [{ "role": "user", "content": "Hello!" }]
}
}'/api/runtime/v1· publicCapability discovery — lists every registered capability and its operations. Registry metadata only; it does not promise provider availability.
/api/runtime/v1/health· Bearer keyAuthenticated health check — proves the key is active and the runtime context resolves. Shares the invocation path's authentication. Returns safe metadata only.
{
"ok": true,
"data": {
"status": "operational",
"requestId": "rtreq_9f2c41e8a7b64d0e9c3f8a2b7d5e1c60",
"identity": {
"projectId": "proj_yourproject",
"environment": "production",
"apiKeyId": "rkey_01h9x2",
"scopes": ["ai.text"]
},
"key": { "status": "active", "lastUsedAt": 1758451200000 },
"lifecycle": ["authenticated", "scope_validated", "capability_resolved",
"model_resolved", "provider_selected", "provider_request",
"response_normalized", "usage_recorded", "charge_recorded"]
}
}Key management — session-authenticated dashboard APIs
These endpoints belong to the Atai dashboard, not to the SDK. They authenticate with your Atai session (cookie), not a runtime key, and ownership is always derived server-side.
/api/runtime/keys?projectId={id}· session/api/runtime/keys?projectId={id}· session/api/runtime/keys/{keyId}· session/api/runtime/keys/{keyId}· session/api/runtime/keys/{keyId}/rotate· session{
"name": "My worker", // optional, max 100 chars
"environment": "production", // "development" | "production"
"scopes": ["ai.text", "email"], // [] = all entitled capabilities
"expiresAt": 1798761600000 // optional absolute epoch-ms
}The create and rotate responses carry the plaintext secret exactly once. It is never persisted in plaintext and never returned again.
Request body
| Parameter | Type | Description |
|---|---|---|
capabilityrequired | string | High-level capability, e.g. "ai.text". See Capabilities. |
operationrequired | string | Provider-neutral operation within the capability, e.g. "chat". |
inputoptional | object | Operation input — validated per capability. Unknown fields are rejected, never forwarded. |
metadataoptional | object | Optional caller metadata. Never treated as identity or authorization. |
Identity fields (userId, projectId, environment, apiKeyId) and pricing fields (credits, price, providerCost) are structurally forbidden in the body — sending them is a validation error. Identity comes exclusively from the authenticated key.
Response envelope
Every response — success or failure — uses one envelope:
{
"ok": true,
"data": {
"requestId": "rtreq_…", // correlation ID — also in x-request-id header
"capability": "ai.text",
"operation": "chat",
"data": { // provider-neutral payload
"id": "gen_…",
"model": "openai/gpt-5.2",
"content": "…",
"finishReason": "stop",
"usage": { "promptTokens": 14, "completionTokens": 42, "totalTokens": 56 }
}
}
}{
"ok": false,
"error": {
"code": "runtime_rate_limited",
"message": "Too many requests. Please retry later."
}
}The correlation ID accompanies every request lifecycle (success and failure) in the x-request-id response header. Include it when contacting support — it connects your request to its exact server-side trace.
Errors & Retries
Every SDK failure is an AtaiError with a normalized code, the HTTP status when one exists, and the server's requestId for support correlation. Runtime-originated failures keep their canonical runtime_* codes; the SDK adds its own atai_* codes for transport and configuration problems. The SDK never reveals which provider Atai used.
import { AtaiError, isAuthenticationError, isRateLimitError } from "@atai-group/sdk"
try {
await atai.ai.chat({ messages: [{ role: "user", content: "Hello" }] })
} catch (e) {
if (e instanceof AtaiError) {
console.error(e.code) // runtime_* or atai_*
console.error(e.status) // e.g. 401, 403, 429, 502
console.error(e.requestId) // correlation ID for support
if (isAuthenticationError(e)) {
// 401 — the key is missing, invalid, revoked or expired: rotate it.
} else if (isAuthorizationError(e)) {
// 403 — the key works but lacks the scope for this capability.
} else if (isRateLimitError(e)) {
const waitMs = e.retry?.afterMs // safe retry metadata when provided
// back off and retry
}
}
}Runtime error codes
| Code | HTTP | Meaning & what to do |
|---|---|---|
runtime_authentication_error | 401 | Invalid or missing API key. Check the key is set and well-formed. |
runtime_key_revoked | 401 | This key was revoked. Rotate to a new key. |
runtime_key_expired | 401 | This key passed its expiry. Rotate to a new key. |
runtime_authorization_error | 403 | The key is not allowed to perform this action. |
runtime_project_access_denied | 403 | The key does not have access to the requested project. |
runtime_capability_not_allowed | 403 | The key's scopes don't include this capability. Create a key with broader scopes. |
runtime_rate_limited | 429 | Atai's per-key limit was hit. Back off using retry metadata, then retry. |
runtime_provider_rate_limited | 429 | The upstream provider is rate limiting. Distinct from Atai's own limit — retry with backoff. |
runtime_insufficient_entitlement | 402 | The account backing this key is out of credits. Top up to continue. |
runtime_invalid_request | 422 | The body is invalid for this capability — check the contract tables above. |
runtime_provider_error | 502 | The upstream provider failed. Safe to retry once; if it persists, contact support with the requestId. |
runtime_provider_timeout | 504 | The upstream provider timed out. Safe to retry. |
runtime_model_unavailable | 400 | The requested model isn't available. Drop the model field or pick another. |
runtime_capability_unavailable | 501 | The capability exists but has no working provider yet. |
runtime_billing_unavailable | 503 | Billing could not complete the charge (fail-closed). Retry shortly. |
runtime_pricing_unconfigured | 503 | This capability isn't enabled for billing yet. Contact the platform administrator. |
SDK error codes
| Code | Meaning |
|---|---|
atai_missing_api_key | No API key was passed to new Atai(). |
atai_invalid_api_key | The key doesn't match the atai_<environment>_<secret> shape. |
atai_invalid_config | Malformed configuration (e.g. bad baseUrl). |
atai_invalid_request | Client-side request validation failed — fix the input before retrying. |
atai_network_error | fetch failed / DNS / connection failure. Check connectivity. |
atai_timeout | The 60-second per-request timeout elapsed. |
atai_aborted | The caller aborted via AbortSignal. |
atai_invalid_response | Malformed JSON or runtime envelope — retry, then report with the requestId. |
Safe to log. Error messages, String(error) and JSON.stringify(error) contain only code/status/requestId/message — never your API key or provider internals.
Limits & Quotas
Requests per key per minute
300
Platform ceiling — project limits may tighten it, never raise it.
Request body size
256 KB
Larger bodies are rejected with 413 before parsing.
In-flight per process
32
Runtime capacity cap — excess requests get a fail-fast 429, never a queue.
Per-request timeout
60 s
Client-side default in the SDK; cancellable via AbortSignal.
Messages per chat call
128
Each message content: 1–100,000 characters.
Key-management rate
20/day
Key creations and rotations per account, abuse protection.
How rate limiting works
- Limits are enforced per API key, per rolling minute. Hit the limit and the runtime answers
429 runtime_rate_limited— invalid keys can never consume provider capacity. - Provider-side rate limits are normalized separately:
runtime_provider_rate_limitedmeans the upstream provider (not Atai) is throttling — retry with exponential backoff. - Concurrency is fail-fast by design: the runtime holds a bounded number of provider slots and returns 429 immediately when full, instead of queueing requests in memory.
Best practices
- Retry 429/502/504 with exponential backoff and jitter; honor
retry.afterMswhen provided. - Set
max_tokensdeliberately — it bounds both latency and credit cost. - Use idempotency keys on checkout creation to make retries safe.
Credits & Billing
Runtime usage is billed in Atai credits, deducted from the account that owns the project — automatically, per request. There is no per-provider billing and no provider account to maintain.
How charging works
Request authenticated
The key resolves to its project and account. Identity and billing context come from the key record — never from the request.
Provider executes
The runtime routes your capability to its provider and normalizes the response.
Usage recorded & charged
Provider-reported usage (tokens, characters, credits — whatever the capability's unit is) is normalized and billed in Atai credits. Only the request lifecycle's charge is recorded; the account sees one clean ledger entry.
- Fail-closed billing. If the billing subsystem cannot complete a charge, the request is denied (
503 runtime_billing_unavailable) rather than served for free. - Unpriced = denied. Operations without an admin-configured price are rejected before any provider spend (
503 runtime_pricing_unconfigured). - Out of credits. Requests fail with
402 runtime_insufficient_entitlementuntil the account tops up. - The SDK doesn't bill. Credits, metering and ledger writes are Atai's server responsibility; the SDK only surfaces usage metadata included in a response.
Platform-level operations (building, analyzing, collaborating) have their own credit costs — see Credits & Billing in the main docs →
Security Best Practices
Keep keys server-side
Atai runtime keys are secret credentials — there is no browser-safe public key type. If a generated application runs entirely in a public browser, the key can be seen by end users. Call the SDK from your server (or a serverless function) and proxy results to the browser. Never compensate by embedding any provider key in the client — the SDK has no fields for them and never will.
// app/api/chat/route.ts (server) — the key never leaves the server
import { Atai } from "@atai-group/sdk"
const atai = new Atai({ apiKey: process.env.ATAI_API_KEY! })
export async function POST(req: Request) {
const { message } = await req.json()
const result = await atai.ai.chat({
messages: [{ role: "user", content: message }],
max_tokens: 500,
})
return Response.json({ reply: result.content })
}Environment hygiene
- Store keys in environment variables or your platform's secret manager — never in source, never in localStorage or cookies (the SDK itself never writes your key anywhere).
- Use a development key while building and a production key in live deployments. Different keys = isolated usage and limits.
- Scope keys narrowly — if a worker only sends email, give it
["email"], not every capability. - Rotate on a schedule and rotate immediately on suspicion of leakage. Rotation is atomic: the old secret dies the moment the new one is issued.
What the runtime guarantees
- Identity is server-derived. userId/projectId/environment come exclusively from the key record — client-supplied identity fields are structurally rejected.
- Keys are hashed at rest. Atai stores only a SHA-256 hash; plaintext is shown once at creation and never retrievable.
- Provider neutrality. Provider names, wire formats and credentials never cross the Runtime boundary — there is nothing provider-specific to leak or configure.
- One-way traffic. The SDK sends nothing anywhere except your requests to the Atai origin. No telemetry, no per-request base-URL overrides, no storage.
Auto-Provisioning
If your application was generated by Atai, the runtime credential is delivered to you — no dashboard step needed. When the build completes, Atai:
- Creates a project-scoped runtime key with least-privilege scopes
- Injects it into your application's secret store as
ATAI_API_KEY - Discards the plaintext — it is never persisted in plaintext, logged, or returned again
import { Atai } from "@atai-group/sdk"
// The key arrives via the secret store as ATAI_API_KEY —
// provisioned automatically at build completion.
const atai = new Atai({ apiKey: process.env.ATAI_API_KEY! })
// Optionally verify the wiring at startup:
const health = await atai.health.check()
if (health.status !== "operational") throw new Error("Atai Runtime not connected")Environment mapping
Build completion → development
The dev preview gets a development key.
Production deploy → production
The live deployment gets its own production key.
Idempotent by design
Provisioning is safe to retry: exactly one active key per (project, environment) exists at any time. If secret injection fails, the freshly created key is revoked immediately — no orphaned credentials — and the retry provisions a fresh one.
Provider credentials never enter your app. The generated application receives only its own project-scoped runtime credential. Provider keys stay server-side at Atai — permanently.
FAQ
Is @atai-group/sdk public?
Yes — it is published to the public npm registry so any application (generated or not) can install it with a plain npm install. The package is proprietary to Atai (all rights reserved): you may use it to talk to the Atai Runtime API, but its source is not open-source.
Which provider is behind ai.chat?
That's the point of the abstraction — you never need to know. Atai selects and operates providers server-side, normalizes every response into Atai's shapes, and never exposes provider names or wire formats through the SDK.
Can I call the Runtime API without the SDK?
Yes. The REST surface is documented above — POST /api/runtime/v1 with a Bearer key and a { capability, operation, input } body works from any language. The SDK exists for typed input/output, client-side validation, normalized errors, and cancellation support.
How do I get an API key for my own (non-generated) project?
Create a project in Atai, open Workspace → Runtime → API keys, and create a key. Generated apps get a key automatically at build time.
What happens when my credits run out?
Runtime requests fail with 402 runtime_insufficient_entitlement until the account is topped up. Keys stay valid — nothing is revoked; the next top-up resumes usage immediately.
Does the SDK work in the browser?
Technically yes (pure fetch, zero dependencies) — but runtime keys are secret credentials with no public-key variant, so a browser-embedded key is visible to end users. Route calls through your server instead. See Security Best Practices.
How do I report a problem with a specific request?
Capture the requestId from the thrown AtaiError (or the x-request-id response header) and contact support — it correlates to the exact server-side trace, including which stage of the lifecycle handled your request.
Is there a sandbox or test mode?
Use a development-environment key: identical behavior and contract, isolated usage records and limits — safe experimentation without touching production data or budgets.
Ready to build?
Install the SDK, verify your key with one health check, and make your first AI call in under a minute.
Related documentation