Skip to content

Tier model

The same set of binaries (api, worker, data-provider, frontend-app) runs three ways. You decide by setting two environment variables — there are no per-tier feature flags and no per-tier code paths.

TierSCANI_CLOUD_URL points atWho runs it
1 — fully self-hostedhttp://data-provider:8082 on your own compose network.You.
2 — semi-managedA hosted data-provider endpoint.You run api + worker + frontend; someone else runs the data-provider.
3 — fully managedA hosted data-provider endpoint.Someone else runs everything.

The full design rationale is in Why the three-tier deployment model.

Pick Tier 1 if…Pick Tier 2 if…Pick Tier 3 if…
You want full control of every byte.You’d rather not run an S3 bucket and a mail transport.You want zero operational burden.
You don’t want any traffic leaving your network.You’re fine with an operator holding your uploads and sending your mail.You’re fine outsourcing the whole stack.
You enjoy ops work, or your environment requires it.You’re an operator running Scani for a small group of users.You’re a single user who wants the easy mode.

Tier 2 is not a way to skip managing provider API keys. COINGECKO_API_KEY, FINNHUB_API_KEY, ETHERSCAN_API_KEY, HELIUS_API_KEY and OPENAI_API_KEY are read by your api and worker on every tier — see What changes.

Terminal window
# Tier 1 — defaults from .env.example
SCANI_CLOUD_URL=http://localhost:8082
SCANI_CLOUD_API_KEY=dev_data_provider_key_change_me_not_prod_safe
DATA_PROVIDER_API_KEY=dev_data_provider_key_change_me_not_prod_safe
# Tier 2 — point api + worker at a hosted endpoint
SCANI_CLOUD_URL=https://data-provider.your-host.example.com
SCANI_CLOUD_API_KEY=<issued by the operator>
# DATA_PROVIDER_API_KEY is not used on the user side in Tier 2 — it
# lives on the hosted data-provider.

The data-provider validates incoming bearers against its own DATA_PROVIDER_API_KEY. In Tier 1, single-tenant mode, it’s the same string as SCANI_CLOUD_API_KEY. In Tier 2+, the hosted data-provider mints per-user / per-deployment keys via its cloud-management surface (gated behind CLOUD_MANAGEMENT_ENABLED=true).

A minted key reaches pricing, AI, chain reads, OG metadata and token search. It does not reach object storage or email: those are internal facades over the operator’s own bucket and mail account, and a minted key gets 403 FORBIDDEN (SC-585). See Tier 2 wiring for the two ways to handle that — the row above claiming Tier 2 means not running an S3 bucket holds only where the operator has granted your key.

  • User integration credentials (exchange API keys, brokerage tokens) always live on your api. Tier-2 operators do not see them.
  • The schema. Same Postgres tables, same indexes, same migrations. Applied explicitly by you on every Tier 1 deploy (Apply migrations); the same scani/migrate image works against a managed Postgres too.
  • The wire contract. tRPC routes, payload shapes, return types are identical across tiers.
  • The product behaviour. No feature is gated by tier.
ConcernTier 1Tier 2 / 3
Object storage, email, OG-metadata fetching, token searchServed by your data-provider.Served by the hosted data-provider.
Outbound calls to CoinGecko / DeFiLlama / Frankfurter / Finnhub / OpenAI / Etherscan / HeliusMade by your api and worker.Made by your api and worker — unchanged.
Provider API keys (CoinGecko, OpenAI, Etherscan, …)You set them in your .env.You set them in your .env — unchanged.
S3_*, SMTP_URL / FASTMAIL_API_TOKENYou set them on your data-provider.Operator sets them on theirs.
Public-internet attack surfacefrontend-app only (api/worker/data-provider are internal).frontend-app only on your side. Hosted data-provider has its own.
SCANI_CLOUD_API_KEY rotationYou rotate it; you also bump DATA_PROVIDER_API_KEY to match.Operator rotates and ships you the new value.