# AI tasks, models, pricing, and results

## Discover capabilities

For new integrations, load user-facing products from `GET https://task.starhk.uk/v1/models?view=products&site_id=<SITE_ID>` and effective pricing from `GET /v1/pricing/rules?site_id=<SITE_ID>`, using the user's bearer token where required. The flat `GET /v1/models?site_id=...` response remains only for backward compatibility. Do not show a provider selector.

Chat-only model pickers may use `GET /v1/models?view=chat-picker&site_id=<SITE_ID>`. It returns the same site-policy-filtered product contract but includes only `task_type=chat` modes, reducing first-load payload without creating a separate source of truth.

> Selecting a mode does not make every mode an asynchronous task. A selected mode with
> `task_type=chat` must use `POST /v1/ai/chat` and the `input.messages` request shape documented in
> [chat.md](chat.md). Never send chat to `POST /v1/tasks`, and never poll a chat creation response.

Render one card or selector entry per `products[]` item. After the user selects a mode, use that
mode's exact `task_model` as the task `model` and its exact `task_type` as the task `task_type`.
Never derive `-t2v`, `-i2v`, `-r2v`, `-edit`, or another child alias from `product_id`; the runtime
mapping is authoritative. Use the selected mode's `form_schema`, `capabilities`, and
`reference_price`, because different modes of the same product can have different inputs and
prices. A callable parent can still contain unavailable modes, so disable any mode with
`callable=false` and optionally explain `unavailable_reason`.

## Model logo contract

Star Task is the source of truth for model logos. An administrator maintains each model's
**Model Logo URL** in Star Admin; frontend sites must consume the URL returned by the runtime
catalog and must not bundle a vendor-to-logo table, infer a logo from `family`, `product_id`, or
`task_model`, or construct an `/applogo/...` path.

- Recommended product catalog (`GET /v1/models?view=products&site_id=...`): render
  `products[].icon` on the product card and model selector. This is Star Task's product-view
  projection of the configured model logo/catalog icon.
- Backward-compatible flat catalog (`GET /v1/models?site_id=...`): render each model's top-level
  `logo_url` field. Do not read the duplicate value from inside `capabilities`.
- Treat a missing or empty URL as “no logo” and show the site's neutral placeholder. Do not fall
  back to a guessed vendor image. Use the returned URL verbatim after the frontend's normal
  `http:`/`https:` URL validation.
- Use the model/product `display_name` as image alternative text. A logo update becomes visible
  after the runtime catalog cache refresh, without a frontend release.

```js
const productLogoUrl = product.icon; // view=products
const flatModelLogoUrl = model.logo_url; // backward-compatible flat view
```

Display `model.reference_price.display_label` beside each model so users see a unit-aware expectation such as `2点/张起`, `12点/秒起`, or separate input/output prices per million tokens. Use `reference_price.details` in a pricing popover or detail sheet. This value is explanatory only: after any billable option changes, call `POST /v1/pricing/quote` with the complete input and display its `cost_points` as the estimated total. Keep the legacy model-level `cost_points` only for backward compatibility; never present it as an unlabeled unit price. If `reference_price.billing_mode` is `fallback`, label it as a fallback rather than a normal selling price.

The site-scoped response is the maximum set the server currently allows for that site. With the default `model_policy_mode=all`, it contains the globally enabled runtime catalog; an optional administrator allowlist may narrow it further. The frontend may independently hide products or modes for its own UX. Compute the display set as the intersection of the fresh response and the site's display configuration; do not add entries from a global or hard-coded catalog. Remove locally cached entries when `model_policy_version` changes or an item disappears.

Prefer a remotely editable site display configuration containing only IDs, for example `product_id` values for whole products and `task_model` values for individual modes. Missing IDs are ignored. This lets a site expose 20 of 30 available models without changing Star Task or blocking the other models at the API layer. Use the server-side `allowlist` only when calls must actually be prohibited, not merely hidden from navigation.

Model metadata and availability are synchronized from RokoAPI and verified against the models callable by Star Task's server-side token. When a synchronized model has no local price, Star Task converts a safe structured RokoAPI USD price at `1 point = USD 0.0074` and publishes that initial default; media charges round up to whole points while text Token rates retain decimals. An existing global/site rule or administrator-adjusted price is never overwritten, and later upstream price changes are review signals rather than automatic repricing. Never infer a selling price from an upstream price list, copy a documentation snapshot into frontend code, or show a model that is absent from the runtime response. Frontend code uses only the Star alias and never chooses a provider.

For every catalog-synced product mode, honor `availability`, `callable`, `priced`,
`supported_modes`, and `form_schema` from the runtime response. Render a model with
`callable=false` as temporarily unavailable and never submit it. Build controls only from
`form_schema` and enforce cross-field hints such as `form_validation.at_least_one`; do not infer
streaming, reasoning, modalities, image limits, resolution, duration, format, or count from a
model, family, or provider name. A model may have text, image, video, audio, and music
capabilities at the same time, so do not treat model type as a single exclusive enum.
When no structured form is supplied, show only the basic inputs that the runtime schema returns.
Submit the flat field names returned by `form_schema`; Star Task maps any catalog-declared
`request_path` such as `metadata.lyrics` before calling the upstream provider.

Each site may inherit Star Task pricing or use a site-specific rule configured by an administrator. Both modes are resolved server-side. The frontend must not submit a price, apply its own markup, or assume another site's rule. Use the site-scoped quote endpoint after option changes and the task creation response as the final charged amount.

An implementation should keep only identifiers in its display configuration and always render the current runtime object:

```js
const catalog = await fetch(
  `https://task.starhk.uk/v1/models?view=products&site_id=${encodeURIComponent(SITE_ID)}`,
).then(response => response.json());

const selectedProducts = new Set(siteConfig.productIds ?? []);
const selectedModes = new Set(siteConfig.taskModels ?? []);
const showAllProducts = selectedProducts.size === 0 && selectedModes.size === 0;

const visibleProducts = catalog.products
  .filter(product => showAllProducts || selectedProducts.has(product.product_id) ||
    product.modes.some(mode => selectedModes.has(mode.task_model)))
  .map(product => ({
    ...product,
    modes: product.modes.filter(mode =>
      mode.callable === true &&
      (showAllProducts || selectedProducts.has(product.product_id) || selectedModes.has(mode.task_model))),
  }))
  .filter(product => product.modes.length > 0);
```

Sites that want to explain temporary outages may retain returned `callable=false` modes as disabled controls instead of filtering them out. They still must not submit those modes.

## Image option contracts

- Read modes only from the returned product's `modes` and the selected mode's `supported_modes`. The recognized image modes are `text-to-image`,
  `image-to-image`, `reference-to-image`, and `image-edit`; show only the modes returned for that
  model and submit the selected mode as `task_type`.
- Build size, resolution, quality, count, and upload controls from `form_schema`. If the catalog
  does not declare a field, do not reconstruct it from a model-name table or a remembered vendor
  contract.
- Show image upload when the selected mode requires an input image or when runtime metadata
  explicitly declares an image field. Send uploads through `input.image_urls`; use declared
  `min_items` / `max_items` when present and do not invent a fallback count.
- Use the selected mode's exact `task_model`. Do not translate model names, endpoint types, or vendor-specific
  speed/quality values in browser code.

Upload a local JPEG, PNG, or WEBP file, up to 24 MB, before quoting and creating the task:

```js
const form = new FormData();
form.append('file', file);

const uploaded = await fetch(
  `https://task.starhk.uk/v1/upload?site_id=${encodeURIComponent(SITE_ID)}`,
  {
    method: 'POST',
    headers: { Authorization: `Bearer ${accessToken}` },
    body: form,
  },
).then(r => r.json());
```

Put `uploaded.url` in both the quote input and the final task's `input.image_urls`. Uploaded inputs live under the R2 `uploads/images/` prefix and expire after two hours. They are temporary model inputs, not durable user assets. Star Task generation outputs use a separate durable prefix and are not subject to this lifecycle.

## Video option and pricing contracts

Video prices are usually per output second but may also depend on generation mode, resolution, quality, speed, or audio. Send duration in the field declared by `form_schema`, and send all other billable choices exactly as declared there. Always calculate the displayed total through `POST /v1/pricing/quote`; never copy a point-per-second table into frontend code. See [pricing.md](pricing.md) for the structured point rule contract.

## Create and poll

This section applies only to asynchronous image, video, audio, and music modes. Chat uses the
separate synchronous/streaming contract in [chat.md](chat.md).

For an ordered ecommerce main-image or detail-image set, use the task collection contract in
[task-collections.md](task-collections.md). Do not submit a browser-side loop of unrelated tasks;
the collection API provides durable progress, partial resume, cancellation, and version history.

```js
const idempotencyKey = crypto.randomUUID();
const created = await fetch('https://task.starhk.uk/v1/tasks', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${accessToken}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    site_id: SITE_ID,
    model: 'nano-banana-2',
    task_type: 'text-to-image',
    input: {
      prompt: 'A calm Hong Kong harbour at dawn',
      quality: '1K',
      image_urls: [uploaded.url], // Omit this field for text-to-image.
    },
    idempotency_key: idempotencyKey,
  }),
}).then(r => r.json());

const taskId = created.data.task_id;
```

Poll `created.data.poll_url` (or `GET /v1/tasks/:id?site_id=...`) about every 3–5 seconds. Stop at `succeeded`, `failed`, or `cancelled`; use a 15–20 minute UI timeout for long video/audio jobs without cancelling the server task automatically.

When a task reaches `failed`, display `task.error` in a modal, toast, or prominent inline error.
Do not replace it with a generic failure string. When an upstream provider explicitly rejects a
prompt under its content-safety policy, Star Task normalizes this field to a user-safe Chinese
message asking the user to edit the prompt and retry. Other provider errors keep their existing
behavior.

For successful audio/music tasks, use `output.audio_url` for an HTML audio player and download
action, and use `output.tracks` when multiple results are present. The detail view should show the
Star task ID, `output.upstream_task_id`, model, status, `created_at`, `completed_at`,
`cost_points`, and `error`. Treat only Star Task output URLs as durable; upstream result links can
expire. Star Task owns any required R2 transfer.

## Charging guarantees

- Star Task resolves the site's effective price and coordinates point consumption/refund.
- The create response contains authoritative `cost_points`.
- `reference_price` is the unit-price display contract; `/v1/pricing/quote` is the parameterized estimate; task creation is the authoritative charge.
- Refresh runtime model/pricing data when its `pricing_version` changes; a frontend release must not be required for an operations-side price update.
- A rule imported from RokoAPI records `source.expr_hash`. It is an operations audit marker; frontend sites still calculate nothing locally and use the Star Task quote.
- Reusing the same idempotency key for the same user action avoids duplicate tasks and duplicate charging.
- A failed task follows Star Task's refund policy. Frontends must not call a point-consume endpoint themselves.

## Result handling

Render only Star Task output URLs. Image, video, and audio outputs from RokoAPI are copied to Star Task R2 storage before success is exposed. If persistence fails, the Roko task is failed/retried rather than reporting a temporary upstream link as durable.

Provider routing is internal. Omit `options.provider`; Star Task ignores legacy frontend provider overrides and prefers enabled RokoAPI routes globally, while models without one keep their current supplier. A RokoAPI-selected task never falls back to Evolink or GRSAI; RokoAPI performs its own channel routing. Disabled models and RokoAPI routes without a local selling price remain unavailable.
