# TrustPix API and CLI

TrustPix is a team wall for AI image and video generation. This page is for people and for AI agents that drive the wall from outside the browser (a script, a Claude Code skill, a CI job). Everything the wall's own page does goes through the same JSON API described here.

Base URL: the wall you signed in to, for example `https://trustpix.vantagecommerceinc.com`.

## 1. Access: API tokens

A token identifies **you** to the wall from outside the browser. It is not a provider key: your Kie AI and fal.ai keys pay the providers and stay in your Settings; the token only lets a script act as your account (queue generations billed to your keys, read the wall, pull picks).

- Mint one: avatar → **Settings** → **API tokens** → label it by where it lives (`macbook`, `ci`) → **New token**. It is shown once.
- Store it as two lines in the repo's gitignored `trustpix/.env`:

```
TRUSTPIX_URL=https://trustpix.vantagecommerceinc.com
TRUSTPIX_TOKEN=tp_xxxxxxxx…
```

- Revoke it from the same list when the machine is retired. Every request records `last_used` on the token.
- Send it as `Authorization: Bearer <token>` on every call. A browser session cookie works too, but scripts should use the token.

## 2. The scripts (easiest path)

Both scripts live in the `trustpix` repo and read `.env`.

```
node generate.mjs --list-models                    # registry with providers + whether you hold the key
node generate.mjs --credits                        # Kie balance; fal key state
node generate.mjs --model nano-banana-2 --prompt "…" --ref logo.png --project site --slug hero --quote
node generate.mjs --model nano-banana-2 --prompt "…" --ref logo.png --project site --slug hero
node generate.mjs --batch plan.json --order 1      # a site image plan (see 5); --order 2 after favouriting order-1 picks
node generate.mjs --upscale <wall file name>       # Recraft crisp upscale of a wall image
node generate.mjs --model kling-3 --prompt "…" --yes   # video refuses without --yes; always --quote first
node pull.mjs --dest ~/Sites/<site>/public/images --project site --favorites [--plan-names]
```

`--ref <local file>` uploads the file to the wall's refs by basename. `--quote` prints the cost and stops. Results land on the wall with full metadata; `pull.mjs` writes each file with a `.json` sidecar (prompt, model, params, cost).

Rules an agent must keep: quote before spending; never run video without the operator's explicit yes; generate at 1K and upscale the picks; favourite the pick before an order-2 run so the plan can find it by slug.

## 3. Endpoints

All bodies and responses are JSON. Errors are `{ "error": "message" }` with a 4xx status.

| Method | Path | What it does |
|---|---|---|
| GET | `/api/me` | Who the token belongs to; `has_kie_key`, `has_fal_key`, `is_admin`. |
| GET | `/api/models` | The registry: `models[]` (key, label, type, lane, blurb, params with allowed values and defaults, providers, costExample, tags, docs, tool, maxRefs), `balances` (`kie: {credits, usd}` and `fal: {usd, currency}`, each with `has_key`, `topup` and `keys` URLs, or `error`; fal answers only admin-scoped keys), `providers` (`{kie, fal}` = keys held). |
| GET | `/api/items` | Every wall file: `name` (the stable id), `type` (image/video), `url`, `bytes`, `mtime`, `meta` (title, model, prompt, refs, params, project, slug, cost_estimate, cost_actual, credits_consumed, provider, favorite, source_item, derived). |
| PATCH | `/api/items/<name>` | Rename: `{ "title": "Matte black coffee mug" }` sets `meta.title` and derives `meta.slug` from it (`project` optional). The file name never changes; downloads and `pull.mjs --plan-names` use the slug. |
| POST | `/api/items/<name>/favorite` | `{ "favorite": true }` |
| POST | `/api/items` | Save a derived image made client-side (the wall's crop tool): `{ "source": "<wall file>", "dataUrl": "data:image/png;base64,…", "derived": "crop", "crop": {x,y,w,h} }` → `{ name, url }`. Cost 0. |
| DELETE | `/api/items/<name>` | Remove a file. |
| GET | `/api/refs` | Reference images (`name`, `url`, `bytes`). |
| POST | `/api/refs` | `{ "name": "logo.png", "dataUrl": "data:…;base64,…" }` (images or video under 9 MB). |
| DELETE | `/api/refs/<name>` | Remove a reference. |
| POST | `/api/quote` | `{ "items": [Item…] }` → per-item `{ ok, label, provider, cost, params, refs, slug }` or `{ ok:false, error }`, `total`, `hasVideo`. Free. |
| POST | `/api/generate` | `{ "items": [Item…], "approveVideo": false, "batch": "name?", "budget": 3 }` → `{ ids, batch }`. Queues jobs; 402 without a provider key or without `approveVideo` for video. |
| POST | `/api/expand` | `{ "plan": <site image plan>, "model": "nano-banana-2", "project": "?", "order": 1 }` → `{ items, skipped, notes, orders, project }` (section 5). |
| GET | `/api/jobs` | Your last 60 jobs with `status` (queued/starting/running/finalizing/done/failed/cancelled), `message`, `files`, `cost`. Polling this also advances the queue. |
| POST | `/api/jobs/<id>/cancel` | Cancel a queued job. |
| GET | `/f/gen/<name>`, `/f/refs/<name>` | The media itself (signed-in only; Range supported). |

### Item shape

```json
{
  "model": "nano-banana-2",
  "prompt": "…",
  "refs": ["logo.png", "gen/site_hero_1700000000000.png", "https://…"],
  "source": "site_hero_1700000000000.png",
  "params": { "aspect_ratio": "16:9", "resolution": "1K", "output_format": "png", "quality": "medium" },
  "project": "site",
  "slug": "hero"
}
```

`title` is optional: without it (and without `slug`) the wall names the file from the prompt (first clause, articles and photo jargon dropped, five words: "Matte black ceramic coffee mug"); people can rename on the wall afterwards. `refs` are names in the refs store, wall files as `gen/<name>`, or URLs. `source` is one wall image as the only ref (tools and edits); an item with `source` and no `project`/`slug` inherits them from that image (slug gets `-up`, `-nobg` or `-edit`), so the result lands beside it. `params` must use the values `/api/models` lists for that model; anything missing takes the default. The planner picks the cheapest provider route you hold a key for; the quote names it.

## 4. Models and tools

`/api/models` is the source of truth. Lanes: `draft` (Nano Banana 2 Lite, FLUX.2 Klein, Z-Image, Grok Imagine, GPT Image 2 low), `quality` (Nano Banana 2, Seedream 5 Pro, FLUX.2 Pro, GPT Image 2 medium), `top` (Nano Banana Pro, GPT Image 2 high), `video` (Kling 3, Veo 3.1, Seedance 2), `tool` (Recraft crisp upscale on fal, Recraft remove background on Kie). Tools take `source` and no prompt. Editing an existing image is a normal generation with `source` set and a prompt describing the change.

## 5. Site image plans

The Batch tab and `generate.mjs --batch` accept a whole plan JSON from the site-images step (`{ cast, region, images: [{ id, filename, type, prompt, aspect_ratio, generation_order, reference_images, … }], … }`). Only `type: "generated"` entries run. Order 1 makes the cast references; favourite the pick of each; order 2 resolves `reference_images` to those favourites by slug. `pull.mjs --plan-names` writes files under the plan's filenames.

## 6. Where things live

- Repo: `jdunham2/trustpix`. Page `public/index.html`, API `functions/api/[[path]].js`, registry `lib/models.mjs`, one recipe per model under `.claude/skills/generate/models/`, the agent skill `.claude/skills/generate/SKILL.md`.
- This document: `/docs/api.md` on the wall; `/llms.txt` points here.
