Ingram Cloud

Documentation

Pulumi provider

Pulumi provider

@ingram-tech/pulumi-ingram-cloud is the infrastructure-as-code path for Ingram Cloud. Declare your tenant's configuration — agents, MCP servers, deployments, webhooks, BYOK model keys — as Pulumi resources instead of imperative setup scripts, so the configuration lives in state and a change is a pulumi up.

The library is published alongside the API so the wrapper tracks the surface it wraps; the API version it targets is pinned per library version.

bun add @ingram-tech/pulumi-ingram-cloud @pulumi/pulumi

Connection

Every resource takes a baseUrl + token. Resolve them once with connectionFromConfig() and spread the result into each resource:

import * as ic from "@ingram-tech/pulumi-ingram-cloud";

// Reads ingram-cloud:token (secret) / :baseUrl from Pulumi config,
// or INGRAM_CLOUD_TOKEN / CLOUD_API_KEY from the environment.
const conn = ic.connectionFromConfig();
# ingram-cloud:token is a tenant-admin token (server-side / CI only).
pulumi config set --secret ingram-cloud:token "$INGRAM_CLOUD_TOKEN"

The token and every credential input are marked as Pulumi secrets, so they are encrypted in state. CRUD runs inline in the Pulumi process over fetch.

Agents

IcAgent is create-or-adopt by slug: it publishes a new immutable version only when the content changed, then rolls it out. The first pulumi up after switching from a setup script adopts the existing agent (matched by slug) rather than recreating it.

slug is the immutable reconcile key; it defaults to the Pulumi resource name. name is a free display label you can rename without churning the agent. Keep agent instructions in your application repo (they are app-owned content) and import them into the Pulumi program:

import { AGENT_SPECS } from "../src/agents"; // app-owned prompts

const support = new ic.IcAgent("support", {
  ...conn,
  // slug defaults to "support"; pass `slug` to decouple the key from the name.
  name: AGENT_SPECS.support.name,            // display label, freely renamable
  instructions: AGENT_SPECS.support.instructions,
  model: AGENT_SPECS.support.model,
  autoMemory: AGENT_SPECS.support.auto_memory,
  // variables: [...], enabledHostedTools: [...], rolloutPercent: 100,
});

export const supportAgentId = support.agentId;

Pulumi owns the agent design, not per-smith runtime state: attaching existing smiths to an agent is a one-time fleet backfill you keep as a script, and new smiths attach at birth in your app code.

Integrations, tools, webhooks, model keys

The rest of a tenant's configuration declares the same way. A common split is to keep agent prompts in the application repo and declare this connective config in your infrastructure stack:

// Your own MCP server (raw URL + a static secret).
new ic.IcMcpServer("acme-mcp", {
  ...conn, serverName: "acme", url: "https://acme.example.com/api/mcp",
  authKind: "static", secret: mcpSecret,
});

// A curated third party from the catalog: gate which tools run, and require
// human approval on the dangerous ones. Per-smith OAuth is collected at run time
// by the hosted connect flow, not declared here.
new ic.IcMcpServer("stripe", {
  ...conn, serverName: "stripe", catalog: "stripe",
  toolAllowlist: ["get_balance", "list_charges", "create_refund"],
  approvalPolicy: [{ match: "create_refund" }],
});

new ic.IcTelegramBot("acme-telegram", { ...conn, botToken });

const hook = new ic.IcWebhook("acme-events", {
  ...conn, url: "https://acme.example.com/api/ic/events",
  events: ["run.completed", "approval.required"],
});
export const webhookSigningSecret = hook.secret; // whsec_… (create-only output)

new ic.IcModelKey("anthropic", { ...conn, provider: "anthropic", apiKey });

IcWebhook.secret is the whsec_… signing secret the API returns exactly once; it becomes a secret Pulumi output, so an infrastructure stack can flow it straight into your runtime environment with no copy-paste. Verify it on delivery the same way as any webhook.

Projects (advanced)

IcProject + IcProjectToken are the tier above a project, for platform stacks that provision projects in bulk. They take an organization key (organization:*, your account master key) instead of a tenant token. The org key provisions projects and mints each one a project-scoped tenant:* token — which you drop straight into that project's runtime environment. The org key reads no run, memory, or smith itself; only the project tokens it mints can (see scopes).

// In a platform stack, ingram-cloud:token is your ORGANIZATION key.
const project = new ic.IcProject("acme", { ...conn }); // adopt-by-name
const icToken = new ic.IcProjectToken("acme-admin", {
  ...conn,
  project: project.projectId, // the project id == the tenant
});

export const acmeProjectId = project.projectId;
export const acmeToken = icToken.projectToken; // secret tenant:* token

IcProjectToken.projectToken is a secret output; it re-mints on any change and revokes on destroy.

Adopting resources a script already created

Agents and webhooks implement read, so you can adopt pre-existing ones:

pulumi import 'ingram-cloud:index:IcAgent' support agt_123…

Agents also self-adopt by slug on first create, so an explicit import is usually unnecessary — a plain pulumi up reconciles the live agent. read treats a 404 as "gone" (so pulumi refresh prunes a resource deleted out-of-band) but still throws on other errors, so a transient API blip can't drop a resource from state.

When to reach for it

Use the provider when your tenant configuration should live in version control and reconcile on deploy. For one-off calls or runtime work, talk to the API directly — see the API reference and the typed wire contract.