Ingram Cloud

Documentation

Auth & tokens

Auth & tokens

Every request carries Authorization: Bearer <token> and IC-Api-Version: 2026-05-01. Tokens are RS256 JWTs; the sub claim encodes identity, so the tenant is implicit in the token and never appears in a URL.

Two token types

Tenant-adminSmith token
sub<tenant><tenant>:<smith id>
Accessevery /v1 operation, in this project onlyonly that one smith's data
Lifetimedays, or non-expiring≤ 24 hours (enforced)
Livesyour server, as a secreta browser or device is fine
Handle prefixtha_live_…thp_live_…

A tenant-admin token carries the tenant:* scope. It grants everything, but is tenant-bounded: every query the API runs filters by the token's own tenant, with no bypass, so it cannot read another project even if leaked across projects you own. Treat it like a database password regardless.

A smith token is cryptographically bound to one smith, so requests for any other smith fail with 403 smith_mismatch, by construction rather than by policy. Hand it to a browser or device so a person can talk to their smith and nothing else.

Minting tokens

Console: tenant-admin keys live at Settings → API keys; a key scoped to one smith is minted from that smith's Tokens tab (Smiths → open a smith → Tokens). Both are shown once. API: minting requires a tenant-admin token:

# Authorization: tenant-admin token (server-side only)
curl https://api.cloud.ingram.tech/v1/tenant/tokens \
  -H "Authorization: Bearer $IC_TOKEN" \
  -H "IC-Api-Version: 2026-05-01" \
  -H "Content-Type: application/json" \
  -d '{ "scope": "smith", "smith_id": "smt_…",
        "permissions": ["runs:read", "runs:write", "memories:read"],
        "ttl_seconds": 3600, "name": "browser session for user_123" }'
# → 201 { "id": "tok_…", "token": "thp_live_<jwt>",
#         "sub": "<tenant>:smt_…", "expires_at": "…" }

For a tenant-admin token: { "scope": "admin", "ttl_seconds": 7776000 } (omit ttl_seconds for non-expiring). GET /v1/tenant/tokens lists minted tokens; DELETE /v1/tenant/tokens/{id} revokes one immediately.

Organization keys & projects

A project is a tenant — the isolation boundary a tenant:* token is bound to. One tier up is the organization (your account), which owns many projects. An organization key carries the organization:* scope with sub = your organization id. It is the master key you hand to infrastructure-as-code, and it reads no run, memory, or smith itself — it only:

  • manages projects — POST/GET /v1/organization/projects, GET/DELETE /v1/organization/projects/{pid}
  • mints a project's tenant-admin token — POST /v1/organization/projects/{pid}/tokens → a tenant:* token whose sub is that project id.
  • manages account creditsGET /v1/organization/billing/balance, …/billing/ledger, and POST …/billing/checkout (the balance is org-level, pooled across the account's projects).
# Authorization: organization key (server-side / IaC only)
curl https://api.cloud.ingram.tech/v1/organization/projects \
  -H "Authorization: Bearer $IC_ORG_TOKEN" \
  -H "IC-Api-Version: 2026-05-01" -H "Content-Type: application/json" \
  -d '{ "name": "acme" }'
# → 201 { "id": "proj_…", "organization": "<org>", "name": "acme" }

# Authorization: organization key (server-side / IaC only)
curl https://api.cloud.ingram.tech/v1/organization/projects/proj_…/tokens \
  -H "Authorization: Bearer $IC_ORG_TOKEN" \
  -H "IC-Api-Version: 2026-05-01" -H "Content-Type: application/json" \
  -d '{ "name": "acme prod" }'
# → 201 { "token": "tha_live_<jwt>", "sub": "proj_…", "scopes": ["tenant:*"] }

You mint the organization key once for your account (it is bootstrapped with your org id, like your first admin token), hand it to your Pulumi stack, and let the IcProject and IcProjectToken resources provision a project and drop its tenant:* token straight into each app's env. A leaked org key can reshape your project list and mint project tokens, but cannot read a single run — only the project tokens it mints can. Creating a project by a name that already exists returns the existing one, so re-running your IaC reconciles rather than errors.

In the console: Settings → Organization is where you rename your organization and mint organization keys. The secret is shown once at mint time and never stored — but each key you mint is listed there afterward (its id and when it was minted/expires), so you can see which keys exist. Rotation today is mint-a-new-one; there is no console revocation yet.

The browser pattern

Never ship a tenant-admin token to a client. Instead, on session start your backend mints a short-lived smith token and hands it to the browser:

browser ──login──▶ your backend ──POST /v1/tenant/tokens──▶ Ingram Cloud
browser ◀──thp_live_… (1h)──┘
browser ──POST /v1/smiths/{their id}/runs──▶ Ingram Cloud   # direct, scoped

Re-mint when it expires (the API returns 401); smith tokens are cheap and stateless.

Permission scopes

Smith tokens carry a subset of the closed vocabulary; omitting permissions grants all of it (still bound to the one smith). Pass only *:read scopes for a read-only token. The console's token form has a checkbox for exactly that.

Scopes gate operations; the smith binding still confines every call to that one smith's data. Tenant-level configuration (agents, the tools registry, webhooks, model keys, token minting) additionally requires a tenant-admin token: a smith token gets 403 tenant_token_required there no matter its scopes.

Scope pairGates
runs:read / runs:writeruns and the smith resource
conversations:read / conversations:writethe conversations resource (threads of runs)
memories:read / memories:writearchival memories + core blocks
connections:read / connections:writeper-smith OAuth connections
deployments:read / deployments:writemessaging (deployment) endpoints
schedules:read / schedules:writecron schedules
approvals:read / approvals:writethe approvals queue
traces:read / traces:writespan waterfalls; write gates span ingestion
usage:read / usage:writeusage rollups & budgets; write gates custom meter events and budget changes
customers:read / customers:writeyour customer objects
files:readmetadata + bytes of files inlined into a conversation

Missing scope → 403 insufficient_scope with the required scope in details.

Security rules

  • Tenant-admin tokens never leave your server. No exceptions, not even "temporarily" in a mobile build.
  • The API verifies RS256 signatures on every request; there is no API-key fallback.
  • Revocation is immediate (DELETE /v1/tenant/tokens/{id}), including for non-expiring admin tokens. Name your tokens so you can find the right one.
  • Webhook payloads are authenticated separately, by HMAC signature; see Events & webhooks.