Model relay and API keys

Call models with restricted API keys, manage deployments, and inspect settled spending.

A Federation with model relay enabled exposes GET /v1/models and POST /v1/chat/completions. Set your OpenAI client's Base URL to your Federation URL plus /v1. Streaming, non-streaming, tools, and usage share the existing AIService execution and settlement pipeline.

Manage your keys

Website users can manage their own keys in Account → API Keys; the full secret appears only in the successful creation dialog. SDK integrations use the account's login user_token to manage keys. A calling key cannot access account, payment, key management, or request history APIs.

import { Embassy } from "@downcity/federation";

const embassy = new Embassy({
  federation_url: "https://your-federation.example",
  user_token,
});
const issued = await embassy.user.relay.create_key({
  name: "Editor",
  allowed_models: ["deepseek-v4-flash"],
  spend_limit: 10000,
  expires_at: null,
});
// Store issued.api_key securely in your calling client; it cannot be retrieved again.
const keys = await embassy.user.relay.list_keys();
await embassy.user.relay.update_key({ key_id: issued.key.key_id, status: "disabled" });
await embassy.user.relay.update_key({ key_id: issued.key.key_id, status: "active" });
await embassy.user.relay.revoke_key(issued.key.key_id);

allowed_models contains public model or route IDs. [] permits no inference; ["*"] permits all published entries. Access to a route does not grant direct access to its model. Names, permissions, limits, and expiration can be updated. Disabled keys can be reactivated; revoked keys cannot. Disabling or revoking a key blocks new requests, while in-flight requests still settle.

spend_limit is measured in Credits; null means no key spending limit. Before inference, the service checks account balance and the key's successfully settled spending. It does not reserve funds. Concurrent in-flight requests can exceed the configured amount, so this is a soft limit. A key has no separate wallet: charges use the account's existing Credits ledger.

Use an OpenAI client

Website Models & setup provides the live catalog, backend-declared pricing, configuration-derived Base URL and placeholder-key examples. It never reads a real key or infers undeclared capabilities.

curl "$FEDERATION_URL/v1/models" \
  -H "Authorization: Bearer $DOWNCITY_API_KEY"

curl "$FEDERATION_URL/v1/chat/completions" \
  -H "Authorization: Bearer $DOWNCITY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"deepseek-v4-flash","messages":[{"role":"user","content":"Hello"}],"stream":true}'

Native POST /v1/ai/stream also accepts calling keys, using the Downcity Model Protocol's model_id and call. With relay enabled, language endpoints preserve the requested model's capabilities and selling price. They do not automatically switch to another model for new media input. Explicitly select a capable model when needed.

Inspect requests and spending

Website Request history offers your own request pages, key filtering and safe attempt details. Unknown metering and unsettled charges remain unknown rather than zero.

const page = await embassy.user.relay.list_requests({ limit: 50 });
const record = page.items[0];
if (record) {
  const attempts = await embassy.user.relay.list_attempts(record.request_id);
  const next_page = await embassy.user.relay.list_requests({ before: record.request_id });
}

The x-request-id response header identifies one logical request; every upstream attempt has its own attempt_id. History includes key ownership, model, channel, tokens, duration, and settlement status. Attempts include time to the first content event. charged_credits is available only after settlement reaches completed. Failed attempts, missing usage, and pending settlement must not be interpreted as zero spending. Logs do not store prompts, responses, calling keys, or provider credentials.

Enable relay in your Federation

const ai = new AIService({
  relay: true,
  credits: {
    precheck: (user_id, needed_credits) => credits.precheck(user_id, needed_credits),
    charge: (input) => credits.charge(input),
  },
}).use(models);
federation.use(ai);

The first startup imports registered language models into the persistent catalog, retaining their adapters and billing functions. Publish subsequent templates through the management SDK. Public capabilities, reasoning options, and selling prices come from registered templates; operators configure availability, channels, upstream mappings, priorities, weights, and routes. The website provides personal key management. Administrators can use Fedman's Channels & routes page to manage public models, channels, deployments and routes, or use the admin SDK below.

const admin = new Embassy({ federation_url, admin_token });
const metadata = await admin.admin.relay.metadata(); // Installed templates and adapters, no credentials
const health = await admin.admin.relay.health(); // Shared cooldown snapshot, not an active probe
const snapshot = await admin.admin.relay.catalog();
// Edit snapshot.catalog, then submit the revision that was read.
await admin.admin.relay.configure(snapshot.catalog, snapshot.revision);

Fedman derives form choices from the live catalog and metadata(). Create, edit, enable/disable and confirmed deletion use the existing catalog contract. References block deletion; there is no implicit cascade. Saves include the read revision; after a 409 conflict, reload the latest catalog and edit again. Registered channels must be installed and retain fixed runtime mappings. OpenAI channels reference server environment variable names; do not enter secret values. Deployment status shows shared cooldowns only. No cooldown record does not imply healthy service; no paid active probe is provided.

The catalog separates Model, Channel, Deployment, and Route. A Model references a capability and price template. A Channel selects the registered or openai_chat_completions adapter. A Deployment maps a public model to a channel and upstream model. A Route chooses deployments of the same public model. Standard OpenAI channels use a credential-free HTTPS API root as base_url and a Federation environment variable name as credential_env; catalog APIs never return credential values. New channels must support the template's declared capabilities.

The default priority_weighted strategy chooses the highest priority tier, then samples positive integer weight values. Direct model calls permit at most two attempts; a route's max_attempts is 1–3. Explicit 401, 403, and 429 rejections allow failover. Network errors, 5xx responses, timeouts, and errors after receiving an upstream stream do not trigger automatic retries. Shared database cooldowns honor Retry-After for 429 responses, capped at five minutes; other failures use short cooldowns. Expired cooldowns allow normal traffic to probe recovery. Channel timeout_ms defaults to 120000 and accepts 1000–300000; adapters must honor the cancellation signal.

Catalog snapshots are cached for five seconds per instance. Updates require revision to prevent lost writes; other instances see changes within one cache interval. Routing adds no model invocation. Key state is read on every request, while settled spending and account balance checks run in parallel. Requests and attempts are persisted before upstream execution, and shared health is read from the database. Measure the resulting latency in your deployment; it depends on database placement and upstream behavior.

RPM, TPM, global concurrency leases, Responses, Anthropic Messages, and OAuth account pools are not provided yet.