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.