Reference

Environment Variables

Common environment variables used by Federation.

VariablePurpose
DOWNCITY_FEDERATION_ADMIN_SECRET_KEYLets Admin City manage Federation
DOWNCITY_FEDERATION_TOKEN_SIGNING_KEYLets Federation issue and validate user_token internally
DOWNCITY_CITY_DATABASE_URLOptional. Specifies the database URL used by Federation
OPENAI_API_KEYExample provider key; recommended to write it into the Federation database through Admin env
OPENAI_BASE_URLExample provider base URL; recommended to write it into the Federation database through Admin env

DOWNCITY_FEDERATION_ADMIN_SECRET_KEY, DOWNCITY_FEDERATION_TOKEN_SIGNING_KEY, and BETTER_AUTH_SECRET are generated automatically on first boot and stored in Federation's env table. Provider API keys should be written into the same table through the Admin API or the fed admin workspace.

If DOWNCITY_CITY_DATABASE_URL is omitted, the default database path is:

.base/downcity.sqlite

How provider env is used

The model handler reads directly from ctx.env:

import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
import { generateText } from "ai";
import {
  Provider,
  buildAssistantMessage,
  type Context,
  type AIProviderChargedOutput,
  type OpenAICompatibleClientConfig,
} from "@downcity/city";
import type { UIMessage } from "ai";

class DeepSeekProvider extends Provider {
  constructor() {
    super({
      id: "deepseek",
      env: { DEEPSEEK_API_KEY: "DeepSeek API Key" },
      envKey: "DEEPSEEK_API_KEY",
      baseURL: "https://api.deepseek.com",
    });
  }

  protected createClient({ apiKey, baseURL }: OpenAICompatibleClientConfig) {
    return createOpenAICompatible({ apiKey, baseURL, name: "deepseek" });
  }

  async text(ctx: Context): Promise<AIProviderChargedOutput<UIMessage>> {
    const apiKey = ctx.env("DEEPSEEK_API_KEY");
    const client = createOpenAICompatible({
      apiKey,
      baseURL: this.baseURL ?? "",
      name: "deepseek",
    });
    const result = await generateText({
      model: client.chat("deepseek-v4-flash"),
      prompt: ctx.input.prompt,
    });
    return buildAssistantMessage(result.text, ctx, { finishReason: "stop" });
  }
}

const deepseek = new DeepSeekProvider();

So the practical rules are simple:

  • you choose the env key names yourself
  • uppercase snake case is recommended
  • read values through ctx.env(key) inside the handler

Write values into the database

A trusted backend can write provider keys through Admin City:

await admin.env.upsert({
  key: "DEEPSEEK_API_KEY",
  value: "sk-xxx",
});

These values are stored in the env table inside the Federation database. Business runtime reads only from that Federation-managed env table and no longer falls back to .env or process environment variables.

See also Provider environment variables.