Smiths
A smith is one of your end-users: the unit of identity, memory, and data isolation. Each one is that person's running clone of an agent, holding their own memory, threads, and connections. (Yes, we call them Smiths 🕶️.) Create one per person from your backend; everything personal (conversations, remembered facts, OAuth connections, deployments, schedules) belongs to it.
Creating smiths
# Authorization: tenant-admin token (server-side only)
curl https://api.cloud.ingram.tech/v1/smiths \
-H "Authorization: Bearer $IC_TOKEN" \
-H "IC-Api-Version: 2026-05-01" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: smith-create-user_123" \
-d '{ "external_id": "user_123", "display_name": "Ada Lovelace" }'
# → 201 { "id": "smt_…", "external_id": "user_123",
# "config_source": "agent", "agent_id": "agt_…", "model": "default", … }
external_idis your own user id. A smith's identity is the pair (external_id,agent_id): one end-user gets one live smith per agent, so the same person can run several agents (an accountant, a tax advisor, an auditor) as several smiths. Creating is upsert-by-(external_id,agent_id): if a live smith already has that pair, the call returns it (200, unchanged) instead of erroring, so "ensure smith" on every login is safe with or without anIdempotency-Key. (The key still gives an exact replay of the original201for 24h.) A new pair returns201. Omitagent_idand it resolves to your default agent, so single-agent apps keep getting one smith per user.GET /v1/smiths?external_id=user_123looks one up by your id;GET /v1/smiths?agent_id=agt_…lists the smiths cloning one agent;GET /v1/smithslists all (cursor-paginated);DELETE /v1/smiths/{id}soft-archives. Deleting frees theexternal_id. Re-creating with the same value provisions a brand-new smith (newsmt_…id), so a wiped smith can always be re-provisioned.- Optional create fields:
locale,timezone,metadata(free-form JSON),customer_id(the billable party, see Customers & metering),auto_memory(setfalsefor a stateless smith), and the config fields below.
Identity, namespaces & segments
external_id is your key for a smith, and it's immutable once set, so
choose it deliberately and namespace it — user:… for signed-in people,
anon:… for logged-out visitors (a random token you mint and cookie). Anonymous
visitors are smiths too: one per visitor keeps their threads (and memory, if you
leave it on) isolated, exactly like a signed-in user.
Namespacing pays off when you segment. external_id_prefix matches one
namespace, on both the smith list and the run feed:
# Authorization: tenant-admin token (server-side only)
# Every logged-out visitor:
curl "https://api.cloud.ingram.tech/v1/smiths?external_id_prefix=anon:" \
-H "Authorization: Bearer $IC_TOKEN" \
-H "IC-Api-Version: 2026-05-01"
# Every anonymous conversation (the segment's run history):
curl "https://api.cloud.ingram.tech/v1/runs?external_id_prefix=anon:" \
-H "Authorization: Bearer $IC_TOKEN" \
-H "IC-Api-Version: 2026-05-01"
The console's Smiths page filter box takes the same prefix. There is no
is_anonymous flag — the namespace is the segment.
When an anonymous visitor signs in, don't rename external_id (it can't change):
keep the smith and PATCH the real identity onto it (display_name,
customer_id, metadata), so its threads and memory carry over. For the full
patterns — smith vs thread, orgs as customers, the claim flow — see
Modeling your users and
Build a support agent.
How effective config resolves
Every smith runs a clone of an agent: instructions, model,
enabled_hosted_tools, auto_memory. Name one in agent_id at create, or omit
it and the smith clones your project's default agent (auto-created on first
use, then editable like any agent). A smith is never agent-less. Where that
config comes from is the config_source field on the smith:
config_source | Meaning |
|---|---|
agent | config comes from an agent version, by reference |
override | agent version + this smith's sparse overrides on top |
The resolution rule, applied at request time:
effective = agent_version(pin ?? rollout(smith_id)) + overrides
GET /v1/smiths/{id} always returns the effective config flattened on the
resource. The fields are what the smith actually runs with, whatever the
source. agent_id, agent_version (the version that resolved), and pin
ride alongside.
instructions is the raw template; if the agent uses
per-smith variables ({{ user.display_name }},
declared {{ … }}), rendered_instructions is what this smith actually runs
with, resolved against its own fields and metadata. That's how you keep
identity in the prompt with zero overrides, so the smith still tracks
rollouts.
Per-smith overrides
On an agent-attached smith, PATCH /v1/smiths/{id} with config fields writes
overrides: a sparse per-smith diff. The agent is never modified from a
smith:
# Authorization: tenant-admin token (server-side only)
curl -X PATCH https://api.cloud.ingram.tech/v1/smiths/smt_… \
-H "Authorization: Bearer $IC_TOKEN" \
-H "IC-Api-Version: 2026-05-01" \
-H "Content-Type: application/json" \
-d '{ "instructions": "Also: this customer is on the enterprise plan." }'
# → config_source: "override"; model/tools still track the agent
Overridden fields stick through rollouts; everything else follows the agent.
"model": "default" means the platform's default model, fine for a first
run, but set an explicit model (see GET /v1/tenant/models for the catalog
your keys unlock) before relying on behaviour.
Attaching, detaching, pinning
- Attach:
PATCH /v1/smiths/{id} { "agent_id": "agt_…" }. The smith jumps to the agent's live version (overrides reset). To adopt existing smiths without changing their behaviour, use the agent's/attachendpoint instead; it stores any drift as overrides (Agents). - Re-home:
{ "agent_id": "" }moves the smith onto your default agent, keeping its current effective config as overrides so behaviour doesn't change. (Smiths are never agent-less.) - Pin:
{ "pin": 3 }locks the smith to version 3 regardless of rollouts;{ "pin": null }returns it to the rollout. Use pins for change-control customers or to reproduce a bug against an old version.
Config history and rollback
Every change to how a smith runs (config edit, override, attach, detach, pin) snapshots the effective config as an immutable revision:
# Authorization: tenant-admin token (server-side only)
curl https://api.cloud.ingram.tech/v1/smiths/smt_…/revisions
# → { "data": [ { "revision": 4, "snapshot": { "instructions": …, "model": … },
# "created_by": "…", "created_at": "…" }, … ] }
curl -X POST https://api.cloud.ingram.tech/v1/smiths/smt_…/revisions/2/restore
A revision is a smith's config-history snapshot, distinct from an agent's published version. History is append-only: a restore re-applies the old snapshot as a brand-new revision. On an agent-attached smith, restore writes the snapshot as overrides. The attachment survives. (Fleet-level history lives on the agent's own versions.)
OAuth connections
A connection lets a smith act on that person's external accounts (Google,
Stripe, …). It is the per-smith credential an oauth MCP tool
forwards, so the tool acts strictly as that human. There are two ways to create
one: the hosted connect flow (Ingram Cloud drives the consent dance — the
easy path, and the only one for catalog
presets) or pushing a token you obtained yourself.
Connecting an end-user's account
Ask Ingram Cloud for an authorize URL for the smith, send the person to it, and IC handles the redirect, exchanges the code, and vaults the token:
# Authorization: tenant-admin token (server-side only)
curl https://api.cloud.ingram.tech/v1/smiths/smt_…/connections/authorize \
-H "Authorization: Bearer $IC_TOKEN" \
-H "IC-Api-Version: 2026-05-01" \
-H "Content-Type: application/json" \
-d '{ "provider": "stripe", "return_url": "https://your.app/connected" }'
# → 200 { "authorize_url": "https://…", "provider": "stripe" }
Open authorize_url in the person's browser. After they consent, the provider
redirects to Ingram Cloud's GET /v1/oauth/callback/{provider}; IC stores the
connection and 302s back to your return_url with ?connection=ok (or
error). When a tool call needs a connection the smith doesn't have yet, the
connection.required event already carries a ready-to-use
authorize_url — surface it, then submit the paused run to
continue.
Whose OAuth client brokers consent is the server's auth.client_mode. The
platform mode (default for catalog presets) uses an Ingram-owned client, so
you register nothing — the consent screen reads "Ingram Cloud". The tenant
mode uses your own client (configure it once at
PUT /v1/tenant/providers/{provider}), so the consent screen carries your
brand. Either way the redirect URI and token vault are Ingram Cloud's.
Pushing a token you obtained yourself
If you'd rather run the OAuth dance under your own client (e.g. a provider not in the catalog), do so and push the resulting grant:
- Configure your OAuth client once per provider:
PUT /v1/tenant/providers/google { "client_id": …, "client_secret": …, "scopes_allowed": […] }(console: Settings → OAuth apps). The PUT replaces every field you send, with one exception:client_secretis write-only and sticky. Omit it and the stored secret is kept (so you can edit scopes without re-sending it); send a new value to replace it, or sendnullto clear it. AGETreports onlyhas_client_secret, never the value. - After the person consents in your app, push their grant:
# Authorization: tenant-admin token (server-side only)
curl https://api.cloud.ingram.tech/v1/smiths/smt_…/connections \
-H "Authorization: Bearer $IC_TOKEN" \
-H "IC-Api-Version: 2026-05-01" \
-H "Content-Type: application/json" \
-d '{ "provider": "google", "scopes": ["calendar.readonly"],
"credential": { "access_token": "…", "refresh_token": "…",
"expires_at": "…" } }'
POST /v1/smiths/{sid}/connections/{cid}/refreshrefreshes using your stored client credentials;GETlists (credentials are encrypted at rest and never read back);DELETErevokes the smith's access.
Connections feed your MCP tools: register an MCP server with
auth.kind: "oauth" and Ingram Cloud forwards the matching provider's token to
your server on each call, so the tool acts as the smith.