Skip to content

Migrating Tier 1 → Tier 2

The migration is two env-var changes and a compose-file edit. No data migration, no downtime if you do it right.

  • You have an issued SCANI_CLOUD_URL and SCANI_CLOUD_API_KEY from the data-provider operator.
  • You have a recent Postgres backup (this should be true regardless; see Backup & restore).
  • You’ve noted which provider keys you currently have set (COINGECKO_API_KEY, OPENAI_API_KEY, etc.). These stay. They are read by your api and worker on every tier, not by the data-provider — see You still need your provider API keys.
  1. Edit .env:

    SCANI_CLOUD_URL=http://data-provider:8082
    SCANI_CLOUD_API_KEY=dev_data_provider_key_change_me_not_prod_safe
    SCANI_CLOUD_URL=https://data-provider.your-host.example.com
    SCANI_CLOUD_API_KEY=<issued key>
  2. Edit docker-compose.prod.yml: comment out the data-provider service and remove data-provider from the depends_on of api and worker (see Pointing api + worker at a hosted endpoint).

  3. Recreate api + worker. The data-provider container stops automatically when you docker compose up -d against a compose file that no longer defines it.

    Terminal window
    docker compose -f docker-compose.prod.yml up -d
  4. Watch the logs:

    Terminal window
    docker compose -f docker-compose.prod.yml logs -f api worker

    The api and worker each log a scaniCloudUrl field on boot — there is no tier field. Confirm it reads your hosted endpoint and not (local fallback):

    Terminal window
    docker compose -f docker-compose.prod.yml logs api worker \
    | grep -E '"scaniCloudUrl"'

    While you are in the logs, check the provider-credentials line too (see Do not remove your provider API keys) — it should read the same before and after the migration.

  5. Verify with a synthetic call:

    • Open the SPA, navigate to the dashboard, check that prices are fresh.
    • Trigger a manual sync on one integration.
    • Trigger a screenshot import (if you use it).
  • No data migration. All your data is in your Postgres. It stays. Sync history, transaction ledger, observations, vaults — all intact.
  • No re-authentication for users. Sessions live in your Postgres; the tier change is invisible to users.
  • No re-encryption of integration credentials. They stay encrypted with your ENCRYPTION_KEY on your machine.

If something is wrong with the hosted endpoint and you need to fall back:

  1. Revert the .env change:

    SCANI_CLOUD_URL=http://data-provider:8082
    SCANI_CLOUD_API_KEY=<your local key>
    DATA_PROVIDER_API_KEY=<same as above>
  2. Uncomment the data-provider service in docker-compose.prod.yml.

  3. docker compose -f docker-compose.prod.yml up -d.

Your provider keys never left, so pricing, AI and chain syncs are unaffected by the round trip in either direction — they were never routed through the data-provider at all.

Email, OG-metadata fetching and token search fall straight back to their local implementations as soon as SCANI_CLOUD_URL points at your own container again.

Once you’re confident in the hosted endpoint:

  • Permanently remove the data-provider service block from your compose file.
  • Remove DATA_PROVIDER_API_KEY and the S3_* block from your .env — the first was only ever the bearer your own container validated, and object storage is now the operator’s bucket.

Four things, and only four — they are the only adapters packages/clients/cloud-client/src/ has:

Moves to the hosted data-providerStays on your api + worker
Object storage (screenshots, file imports)Pricing (CoinGecko, DeFiLlama, Frankfurter, Finnhub)
Email transport (Fastmail JMAP / SMTP)AI inference (OpenAI)
Open Graph metadata (institution logos)Chain calls (Etherscan, Helius, Bitcoin, Tron, TON)
Token search (symbol → identity)Every user-credentialed exchange and brokerage integration

So SMTP_URL, FASTMAIL_API_TOKEN and S3_* can come out of your .env — magic-link emails go through the operator’s transport and uploads land in their bucket. The provider API keys cannot.