Ingram Cloud

Documentation

TypeScript SDK

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

  • . — the schemas Zod map plus the SSE/webhook event types (EVENT_TYPES, webhookEvent, streamFrame, …).
  • ./schemas — just the Zod schemas map.
  • ./zod — the same schemas as individual named exports, one module per resource.
  • ./responses — the IC* TypeScript response types. Zod-free.
  • ./clientIngramCloud, 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.