@japan-ai-inc/studio-sdk
v0.2.0-beta.1
Published
TypeScript SDK for Japan AI Studio platform APIs
Downloads
119
Readme
@japan-ai-inc/studio-sdk
TypeScript SDK for the Japan AI Studio API. Provides typed, ergonomic access to Agents, Custom Objects, Storage, Workflows, and the Pages Rendering Service (PRS) platform.
Installation
Node.js 22 or later is required for the Node SDK and CLI.
npm install @japan-ai-inc/studio-sdkCLI (jai-studio)
Install the published CLI globally:
npm install --global @japan-ai-inc/studio-sdkConfigure a profile
# Interactive prompt (recommended — key never appears in shell history)
jai-studio config set my-project
# Piped from a secret manager (CI/automation)
vault read -field=key secret/jai | jai-studio config set my-project --key-stdin
# Environment variable
JAI_STUDIO_API_KEY=pak_... jai-studio config set my-projectSecurity: Avoid passing API keys as CLI arguments — they are visible in shell history and process listings. Use the interactive prompt,
--key-stdin, or theJAI_STUDIO_API_KEYenvironment variable instead.
Supported Usage Modes
This limited beta is primarily for developing Studio Apps locally. Production Studio App hosting runs through the Pages Rendering Service (PRS).
| Mode | Runtime | Authentication | Available modules | Supported use |
| ------------------ | ------------------------------ | -------------------------------------------------------------------------------- | --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Direct development | Node.js 22 or later | Developer-owned Project API key | agents, members, objects, storage, workflows; Pages via CLI | Local development and explicitly controlled development automation. Arbitrary external production hosting or integration is not part of the beta support contract. |
| PRS browser | Browser inside PRS | PRS session; no API key | agents, objects, storage, platform | Browser code for a PRS-hosted Studio Page. |
| PRS server runtime | Node.js 22 or later inside PRS | Platform-managed Project API key injected into server-only runtime configuration | agents, members, objects, storage, workflows | Server code within PRS hosting. This is not an external integration mode. |
Only Node.js 22 or later is verified for the Node SDK and CLI. Browser support is limited to code running inside PRS.
Direct Development with a Project API Key
Use a developer-owned Project API key for local server-side development or explicitly controlled development automation. The API technically accepts valid Project API keys, but that capability does not create a supported arbitrary external production-hosting contract.
Project API keys are secret-equivalent. They must never enter browser bundles, client-side JavaScript, browser-public environment variables, logs, or source control.
import { createClient } from "@japan-ai-inc/studio-sdk";
const studio = createClient({
baseUrl: "https://api.japan-ai.co.jp",
apiKey: "pak_your_api_key", // Get this from Studio → Settings → API Keys
});
// All modules available: agents, members, objects, storage, workflows
const agents = await studio.agents.list();
const result = await studio.agents.invoke("agent-id", { prompt: "Hello" });Canonical Agent UUIDs are sent directly to /chat/v2; the SDK does not make a
separate Agent detail request to translate them to labels. Non-UUID identifiers
retain the legacy label-resolution path.
Available modules: agents, members, objects, storage, workflows
PRS Browser — No API Key
For browser code deployed as a Japan AI Studio Page, PRS provides the current session. Do not configure or read a Project API key in browser code.
import { createClient } from "@japan-ai-inc/studio-sdk";
// On PRS, baseUrl can be empty — fetch() uses relative paths.
// Auth is handled by PRS session cookies automatically.
const studio = createClient({ baseUrl: "" });
// agents.invoke() auto-detects PRS and routes through /api/agent-chat
const result = await studio.agents.invoke("550e8400-e29b-41d4-a716-446655440000", { prompt: "Hello" });
// Platform module — PRS-only features
const context = await studio.platform.waitForReady();
console.log(context.orgName, context.userName);
await studio.platform.openTask(data, "Review this");Available modules: agents (auto-routes via PRS), objects, storage, platform
PRS Server Runtime — Platform-Managed Key
PRS may inject a platform-managed Project API key into server-only runtime configuration for a hosted app. Server code may use that value with the normal Node client. Never return it from a runtime-config endpoint or forward it to browser code.
import { createClient } from "@japan-ai-inc/studio-sdk";
const apiKey = process.env["JAI_PROJECT_API_KEY"];
if (!apiKey) {
throw new Error("Missing PRS server runtime API key");
}
const studio = createClient({
baseUrl: "https://api.japan-ai.co.jp",
apiKey,
});This key lifecycle is owned by PRS hosting. It is not a credential model for arbitrary external production deployment.
Available modules (PRS server runtime): agents, members, objects,
storage, workflows. The platform module is for PRS browser integration;
Pages deployment remains a development/control-plane operation rather than a
hosted app server-runtime capability.
Distributable Studio App Checklist
Before handing a Studio Page app to the publish/install workflow:
- Keep source-project resource IDs out of source code and browser assets.
- Put supported development-time Agent, Workflow, and Custom Object IDs in
root
.env*files under stable, descriptive server-side keys. - Never put Project API keys or source-project IDs in
NEXT_PUBLIC_*,VITE_*,REACT_APP_*, orPUBLIC_*values. - Keep storage configuration as logical paths or prefixes rather than storage UUIDs.
- Initialize browser SDK code with
createClient({ baseUrl: "" }); only server-runtime code may consume a platform-managed key. - Activate the SDK version only after a compatible PRS runtime is deployed.
How PRS detection works:
- The SDK checks for
window.JapanAI(injected by PRS at runtime) - When detected,
agents.invoke()routes through PRS's/api/agent-chatinstead of/chat/v2 - Other PRS-enabled modules (
objects,storage) go through PRS's/api/v1/*proxy - No code changes needed — the same SDK calls work in both environments
Limited-Beta Support
The beta audience is invited Studio App developers from existing Japan AI customers and partners. Contact your Japan AI account representative for access, support, or suspected security issues. Public package availability by itself does not expand support entitlement.
createClient(options)
| Option | Type | Required | Description |
| ----------- | -------------- | -------- | --------------------------------------------------------------------------------- |
| baseUrl | string | Yes | Studio API base URL (empty in PRS browser); with apiKey, HTTPS except loopback |
| apiKey | string | No | Project API key for server-side modes; never used in PRS browser |
| invokeUrl | string | No | Custom agent invoke URL; with apiKey, it must have the same origin as baseUrl |
| timeoutMs | number | No | Default timeout (default: 30s) |
| fetch | typeof fetch | No | Custom fetch implementation |
Returns a StudioClient with lazy-initialized module accessors: objects, agents, members, workflows, storage, platform.
Agents
Chat with AI agents, manage rooms, and retrieve message history.
List Agents
const { agents, totalCount, nextCursor } = await studio.agents.list({
limit: 10,
search: "support",
});Get Agent
const agent = await studio.agents.get("agent-id");
// agent.id, agent.name, agent.label, agent.description, agent.enabledInvoke (Non-Streaming)
const result = await studio.agents.invoke("550e8400-e29b-41d4-a716-446655440000", {
prompt: "Summarize this document",
sessionId: "room-id", // optional — continues conversation
model: "gpt-4o", // optional
temperature: 0.7, // optional
systemPrompt: "...", // optional
isEphemeral: true, // optional — don't persist to history
});
console.log(result.text); // AI response
console.log(result.sessionId); // Room ID for follow-ups
console.log(result.status); // "succeeded" | "failed"
console.log(result.references); // Source references (if any)Invoke (Streaming)
for await (const event of studio.agents.invokeStream("550e8400-e29b-41d4-a716-446655440000", { prompt: "Write a haiku" })) {
if (event.type === "delta") {
process.stdout.write(event.content); // Stream text chunks
}
if (event.type === "error") {
console.error(event.text);
}
}Rooms & Message History
// Create a new chat room
const room = await studio.agents.createRoom("agent-id");
console.log(room.roomId);
// Get messages in a room
const { messages, nextCursor } = await studio.agents.getMessages("room-id", {
limit: 20,
cursor: "next-page-cursor",
});
// messages[0].id, .role, .content, .createdAtPRS Behavior
When running inside the Pages Rendering Service (PRS), invoke() and
invokeStream() automatically route through /api/agent-chat instead of
/chat/v2. The SDK detects PRS via window.JapanAI and classifies the first
argument after trimming it:
- A canonical 8-4-4-4-12 hexadecimal UUID (case-insensitive, with no
version/variant restriction) is normalized to lowercase and sent as
agent_id. PRS resolves and authorizes that ID from the authenticated Page's release-managed runtime metadata. An ID not declared by that Page is rejected; it does not fall back to label routing. - Any other non-empty string is sent as the legacy
agent_label. For example,"agent-id"is a label, not a stable Agent ID. - An empty or whitespace-only string sends neither field, allowing PRS to use its configured fallback Agent.
SDK-generated requests never contain both identity fields. A legacy Agent whose
label is itself a canonical UUID must use the low-level
studio.platform.chat({ agentLabel }) API to force label routing; that path does
not use release-managed Agent ID resolution or its version/runtime pinning.
Outside PRS, the existing Agent lookup and invocation path remains unchanged.
The compatible PRS agent_id contract must be deployed before an SDK version
using UUID routing is released or activated. An older PRS may ignore
agent_id and invoke its fallback Agent instead.
Objects
Manage custom object schemas and their records.
Object Definitions
// List all objects
const { data } = await studio.objects.list();
// Create an object
const obj = await studio.objects.create({ objectName: "contacts" });
// Get / Update / Delete
const obj = await studio.objects.get("objectId");
await studio.objects.update("objectId", { displayName: "Contacts" });
await studio.objects.delete("objectId");Records (via define<T>())
define<T>() returns a typed handle for record operations on a specific object.
type Contact = { name: string; email: string; company?: string };
const contacts = studio.objects.define<Contact>("contacts");
// CRUD
const record = await contacts.create({ name: "Acme", email: "[email protected]" });
const fetched = await contacts.get("record-id");
const updated = await contacts.update("record-id", { company: "Acme Inc" });
await contacts.delete("record-id");
// List with pagination
const page = await contacts.list({ limit: 20, cursor: "next-cursor" });
// page.data → RecordItem<Contact>[]
// page.hasNextPage → boolean
// page.cursor → string | null
// Search with filters
const results = await contacts.search({
filters: [{ field: "name", operator: "contains", value: "Acme" }],
sort: [{ field: "createdAt", direction: "desc" }],
limit: 50,
});
// Bulk create (up to 1000 records)
const created = await contacts.bulkCreate([
{ name: "Alice", email: "[email protected]" },
{ name: "Bob", email: "[email protected]" },
]);Fields
const contacts = studio.objects.define<Contact>("contacts");
// List fields
const fields = await contacts.listFields();
// Create a field
const field = await contacts.createField({
fieldName: "phone",
displayName: "Phone Number",
dataType: "phone",
fieldNum: 5,
});
// Update / Delete
await contacts.updateField("fieldId", { displayName: "Mobile" });
await contacts.deleteField("fieldId");Search Filter Operators
| Operator | Description |
| ------------------------- | ----------------------- |
| eq | Equal |
| ne | Not equal |
| gt / gte | Greater than / or equal |
| lt / lte | Less than / or equal |
| contains | String contains |
| startswith / endswith | String prefix/suffix |
| in / notin | Value in/not in array |
| isnull / isnotnull | Null check |
| exists | Field exists |
| regex | Regex match |
| between | Range check |
| jsoncontains | JSON field contains |
Storage
Upload, download, and manage files and folders.
List Files
const { files, folders, totalCount, nextCursor } = await studio.storage.list({
path: "/documents",
recursive: true,
limit: 50,
orderBy: "createdAt",
orderDirection: "desc",
});Upload Files
// Single file
const result = await studio.storage.upload(file, { path: "/uploads" });
// Multiple files (up to 20)
const result = await studio.storage.upload([file1, file2, file3], {
path: "/uploads",
});
// result.files → StorageFile[]
// Atomically replace files with matching names in the target path.
// Existing rows keep the same fileId; non-conflicting files are created.
const replaced = await studio.storage.upload(file, {
path: "/uploads",
conflictResolution: "replace",
});Download
// Download file content
const response = await studio.storage.download("file-id");
const blob = await response.blob();
// Get a signed download URL
const { downloadUrl, expiresAt } = await studio.storage.getDownloadUrl("file-id", { expiresInMinutes: 60 });
// Download an entire folder as a zip
const response = await studio.storage.downloadFolder("folder-id", {
maxSizeMb: 100,
});File Metadata & Deletion
const metadata = await studio.storage.getMetadata("file-id");
// metadata.fileId, .fileName, .path, .mimeType, .sizeBytes
await studio.storage.deleteFile("file-id");
// { fileId, message }Folders
// Create a folder
const folder = await studio.storage.createFolder({
folderName: "reports",
path: "/documents",
});
// List folder contents
const contents = await studio.storage.listFolderContents("folder-id", {
recursive: true,
limit: 100,
});
// Delete a folder
const result = await studio.storage.deleteFolder("folder-id");
// result.deletedCount — number of items deletedResumable Upload (Large Files)
For large files, use the two-step prepare → finalize flow:
// 1. Prepare — get a signed upload URL
const prepared = await studio.storage.prepareUpload({
fileName: "large-video.mp4",
fileSize: 500_000_000,
mimeType: "video/mp4",
path: "/media",
});
// 2. Upload directly to storage (using the signed URL)
await fetch(prepared.uploadUrl, {
method: "PUT",
headers: prepared.requiredHeaders,
body: fileContent,
});
// 3. Finalize — register the file in Studio
const file = await studio.storage.finalizeUpload({
storageId: prepared.storageId,
fileName: "large-video.mp4",
fileSize: 500_000_000,
mimeType: "video/mp4",
});To atomically replace an existing file with the same name and path, pass
conflictResolution: "replace" to prepareUpload() and send the returned
replaceIntent back unchanged when finalizing:
const prepared = await studio.storage.prepareUpload({
fileName: "large-video.mp4",
fileSize: 500_000_000,
mimeType: "video/mp4",
path: "/media",
conflictResolution: "replace",
});
await fetch(prepared.uploadUrl, {
method: "PUT",
headers: prepared.requiredHeaders,
body: fileContent,
});
const file = await studio.storage.finalizeUpload({
storageId: prepared.storageId,
replaceIntent: prepared.replaceIntent,
fileName: "large-video.mp4",
fileSize: 500_000_000,
mimeType: "video/mp4",
path: "/media",
});Workflows
Trigger and monitor workflow runs.
List Workflows
const { workflows, totalCount, nextCursor } = await studio.workflows.list({
limit: 10,
});By default only workflows with a published snapshot are returned. Use status
to include drafts:
// Draft-only workflows (no published snapshot)
const drafts = await studio.workflows.list({ status: "draft" });
// Everything — each row carries isPublished
const all = await studio.workflows.list({ status: "all" });
for (const wf of all.workflows) {
console.log(wf.name, wf.isPublished);
}Get Workflow
const workflow = await studio.workflows.get("workflow-id");
// workflow.id, .name, .description, .enabled, .isPublishedRun a Workflow
// Fire-and-forget
const { runId, status } = await studio.workflows.run("workflow-id", {
inputs: { documentUrl: "https://..." },
});
// Poll for result
const result = await studio.workflows.getRunResult(runId);
// result.status: "RUNNING" | "SUCCEEDED" | "FAILED"
// result.outputs: Record<string, unknown>Run and Wait
Blocks until the workflow completes (default timeout: 5 minutes):
const result = await studio.workflows.runAndWait("workflow-id", {
inputs: { query: "market trends" },
timeout: 120, // seconds
});
if (result.status === "SUCCEEDED") {
console.log(result.outputs);
} else {
console.error(result.error);
}Members
List the members of the current project.
Direct development may use a Project API key. PRS-hosted page apps require PRS
to allow-list GET /api/v1/project-members before this module can be used in
browser mode without an API key.
List Members
const { members, totalCount, nextCursor } = await studio.members.list({
search: "ada",
limit: 50,
cursor: "next-page-cursor",
});
// members[0].id, .principalId, .principalType, .name, .email, .avatarUrl, .createdAtPlatform (PRS) — Beta
Beta: PRS is an internal runtime. This module's API may change between minor versions. Pin to an exact SDK version if you depend on it.
Browser-only module for apps running inside Japan AI's Pages Rendering Service. This module does not require baseUrl or apiKey — PRS injects authentication automatically.
Detect PRS
if (studio.platform.isAvailable()) {
const context = studio.platform.getContext();
console.log(context.orgName, context.userName);
}Wait for PRS Ready
PRS injects window.JapanAI asynchronously. Use waitForReady() to wait:
const context = await studio.platform.waitForReady(5000); // timeout in ms
if (context) {
// PRS is ready
console.log(context.userId, context.projectId, context.pageId);
} else {
// Not running on PRS, or timed out
}Platform Context
getContext() returns:
type PlatformContext = {
userId: string;
email: string;
userName: string;
memberRole: string;
orgId: string;
orgName: string;
projectId: string;
pageId: string;
customObjects: Record<string, string>; // name → objectId
};Open Task
Delegate data to an agent task in the Studio UI:
const result = await studio.platform.openTask(
[{ name: "John", email: "[email protected]" }], // data
"Please review this contact", // user prompt
{
agentLabel: "review-agent",
isTemporary: false,
isEphemeral: false,
}
);
console.log(result.roomId);Chat (Low-Level PRS Proxy)
Direct access to PRS's /api/agent-chat endpoint (prefer studio.agents.invoke() instead — it auto-routes on PRS):
Use agentLabel here when you intentionally need the legacy label path,
including for a label that is itself shaped like a canonical UUID. Label-only
calls do not use release-managed Agent ID resolution or its version/runtime
pinning.
const response = await studio.platform.chat({
messages: [{ role: "user", content: "Hello" }],
agentLabel: "support-agent",
model: "gpt-4o",
temperature: 0.7,
});
console.log(response.content, response.roomId);Error Handling
Each module throws its own error class, all extending SdkApiError:
import { ObjectsApiError, AgentsApiError, StorageApiError, WorkflowsApiError } from "@japan-ai-inc/studio-sdk";
try {
await studio.agents.invoke("agent-id", { prompt: "hello" });
} catch (err) {
if (err instanceof AgentsApiError) {
console.error(err.statusCode); // 401, 404, 500, etc.
console.error(err.message); // Human-readable error
console.error(err.responseBody); // Raw error response
}
}Timeouts & Cancellation
Default timeout is 30 seconds. Override per-request or globally:
// Global timeout
const studio = createClient({ baseUrl: "...", apiKey: "...", timeoutMs: 60000 });
// Per-request timeout
await contacts.list({ limit: 10 }, { timeoutMs: 5000 });
// Cancel with AbortController
const controller = new AbortController();
const promise = contacts.search({ filters: [...] }, { signal: controller.signal });
controller.abort();Agent invoke has longer defaults: 5 minutes for invoke(), 10 minutes for invokeStream().
Versioning & Stability
This package follows Semantic Versioning.
Pre-1.0 policy (0.x.y): The SDK is in active development. Minor version
bumps (0.2.0, 0.3.0) may contain breaking changes to method signatures,
error shapes, or module structure. Patch versions within the same minor are
backwards-compatible bug fixes and security patches.
Recommendation: Pin to an exact version in package.json until 1.0:
"@japan-ai-inc/studio-sdk": "0.2.0-beta.1"What counts as a breaking change:
- Removing or renaming an exported function, class, or type
- Changing the signature of a public method (required params, return type)
- Changing error class hierarchy or
statusCodesemantics - Removing a CLI command or changing its required arguments
What does NOT count as breaking:
- Adding new optional parameters, methods, or exports
- Improving error messages
- Bug fixes to match documented behavior
See CHANGELOG.md for release history.
