# Demo 4: Brand job with a token budget

**Port:** 3004 · **Postgres schema:** `jobs` (migration slot `2026102404xxxx`, optional) · **Services:** OpenRouter

## Demo moment

The owner drops a bag of files (two logos, a brand PDF page, or a website URL). A job card appears: "Brand theme · budget €20". The platform creates a dedicated OpenRouter key capped at the budget; a usage meter ticks while the agent works; a theme comes back, passes contrast checks, and the preview pages switch to the new brand. The job closes, the key is disabled, and the leftover shows as a refund.

## Inputs

- `theme.schema.json` (output), `job.schema.json` (job record)
- `@demo/ui` `applyTheme` for the preview
- A sample bag of files in `fixtures/` (prepare two or three fictional brands)

## Flow

1. Create a `Job` (`kind: agent_job`, `token_budget: 2000`).
2. With the management key, create an OpenRouter API key with `limit` = budget converted to USD (`EUR_TO_USD`). Store its identifier on the job.
3. Run the agent with that key and a vision-capable model (`BRAND_MODEL`): extract colours, font suggestion and name; return JSON against `theme.schema.json`.
4. Validate; compute WCAG contrast for primary/primary_foreground and foreground/background; fix or flag.
5. Poll the key's `usage` and `limit_remaining` for the meter.
6. Close: disable the key; refund = budget − usage.
7. Preview: a sample admin page and booking page themed with `applyTheme`.

## Env

`OPENROUTER_MANAGEMENT_KEY`, `BRAND_MODEL`, `EUR_TO_USD`

## Fallback

A pre-generated theme per sample brand; the meter replays recorded usage values.

## Done when

- The theme validates and the preview switches.
- The meter shows usage against the limit from the key itself.
- The key is disabled after the job.

## Out of scope

Real payment for the budget (optional: a PaymentIntent and a partial refund for the leftover), uploading to storage.
