TypeScript SDK
@ingram-cloud/sdk is the API wire contract in TypeScript: Zod
request/response schemas, SSE and webhook event types, and JSON response
types — plus a typed management-plane client built on the same schemas.
The schemas are hand-authored and are the source of truth for the wire — the
API emits its OpenAPI document from these same schemas, and the IC* response
types are inferred from them.
npm install @ingram-cloud/sdk
Chat itself stays on the OpenAI-compatible surface —
use the openai SDK or the AI SDK adapter for that. Reach for
this package to manage resources (smiths, agents, deployments, tenant
config), validate wire bodies at runtime, or type the JSON you read
back.
Call the API
The client entry point is IngramCloud, a typed REST client for the
management plane. Method inputs are inferred from the same Zod schemas the API
validates requests with, so the client can't drift from the contract — and the
imports are type-only, so it pulls in no runtime dependency beyond fetch.
import { IngramCloud } from "@ingram-cloud/sdk/client";
// Authorization: tenant-admin token (server-side only)
const ic = new IngramCloud({ token: process.env.INGRAM_CLOUD_TOKEN! });
const smith = await ic.smiths.create({ external_id: "user-42" });
const page = await ic.smiths.list({ limit: 50 });
const run = await ic.smiths.runs.create(smith.id, {
input: [{ role: "user", content: "hello" }],
});
token also accepts a function minting a short-lived token per request.
Smith-scoped calls made with a tenant token name the acting smith per call —
ic.conversations.list({}, { smith: smith.id }) sends the IC-Smith-Id
header. A non-2xx throws ICError carrying the HTTP status, the error
envelope's code, and the X-Request-Id.
Native lists return the cursor page { data, next_cursor, has_more }; pass
next_cursor straight back as cursor. OpenAI-mirrored lists (files, vector
stores, conversations) return the OpenAI list envelope; pass last_id back
as after. Streaming stays raw:
ic.smiths.runs.stream(...) returns the SSE Response unconsumed for you to
pump.
Validate a body
The schemas map holds one Zod schema per request and non-streaming response
body, keyed by its wire name. Parse against it to validate at the boundary:
import { schemas } from "@ingram-cloud/sdk";
const agent = schemas.AgentIn.parse(input); // throws on a contract mismatch
Type a response
The responses entry point is Zod-free IC* interfaces for the JSON response
bodies — import type them so no runtime dependency is pulled in:
import type { ICSmith, ICRun, ICAgent } from "@ingram-cloud/sdk/responses";
function render(smith: ICSmith) {
/* … */
}
Exports
.— theschemasZod map plus the SSE/webhook event types (EVENT_TYPES,webhookEvent,streamFrame, …)../schemas— just the Zodschemasmap../zod— the same schemas as individual named exports, one module per resource../responses— theIC*TypeScript response types. Zod-free../client—IngramCloud, the typed management-plane client. Zod-free at runtime.
The OpenAPI document itself is served by the API at
/openapi.json, emitted from these schemas.
Coverage
Every resource the API validates with Zod — request bodies and non-streaming JSON
responses — is typed as precise Zod, and the IC* types are inferred from the
same schemas. A handful of endpoints aren't part of the typed surface, because a
client call returns one JSON value and these don't: the streaming/union
endpoints (/runs, /chat/completions, /responses — each a stream or JSON
from one handler), deployment webhook acks, and the OAuth redirect. Their live
frames are the hand-authored {v:1} webhook/feed envelope and SSE run-stream
types. (The OpenAPI spec does
describe all of them — it can carry two media types on one response, where a
typed client method cannot.)
Two surfaces are typed as plain TypeScript rather than Zod, because the API
doesn't validate them with Zod either and a schema would be shared with nothing:
/embeddings (a hand-shaped OpenAI-compatible route) and the spans a caller
pushes to /traces:ingest (deliberately open on the wire, so an OTel exporter's
payload is normalized rather than rejected). Both are still fully typed on
IngramCloud.
The OpenAI-compatible stream chunks themselves are standard — use the @ai-sdk/*
types for those rather than redefining them.
Ships compiled ESM alongside the TypeScript source. Node and bundlers load the compiled build — no transpile config needed — while types resolve straight to the source, and Bun runs the source directly.