---
title: "Score a composition (the standalone sighted judge)"
method: POST
path: "/v1/projects/{id}/score-composition"
tags: ["evidence"]
---

# Score a composition (the standalone sighted judge)

`POST /v1/projects/{id}/score-composition`

## Request

- Base URL: `https://api.cueframe.ai`
- URL: `https://api.cueframe.ai/v1/projects/{id}/score-composition`
- Auth: one of:
  - HTTP bearer
  - OAuth 2 (authorization code, authorization URL `https://api.cueframe.ai/api/auth/mcp/authorize`, token URL `https://api.cueframe.ai/api/auth/mcp/token`, scopes: `projects:write`)
  - API key in header `PAYMENT-SIGNATURE`

Queue the server-side judge: it samples eval beats, renders them, and grades editorial/spatial/brand/caption (0–10 + weighted composite + worst-first critique). Your REGISTERED CRITICS run too (register_critic) — rules + rubric verdicts ride the result as clientScores, exactly like the Director's gates evaluate them (evaluator-webhook critics run in the Director lane only). Returns a jobId — wait_job(kind='verify') for the result, which ALSO carries a checkpointId: the verdicts mint a durable kind:'score' Checkpoint (the same evidence noun the Director's gates produce). Stateless — nothing to open, warm or close; the compose_verify meter fires on the job's success terminal.

## Path parameters

- `id` string, required

## Request body

- ScoreCompositionRequest
  - `composition` unknown
  - `compositionId` string: Judge a SPECIFIC persisted composition of this project (create_render parity). Absent ⇒ the active one. A stale/foreign id is a 404 — never a silent fall-through.
  - `session_id` string: Compose-session id: judge that session's SCRATCH composition (what the session has been editing) instead of the active row. Ignored when `composition` is passed.
  - `editorialIntent` string
  - `brandContext` string

## Response `202`

Score job queued.

- JobEnqueueResponse
  - `jobId` string, required: Poll GET /v1/jobs/{jobId} / wait_job until terminal.
  - `jobKey` string, required: Deterministic identity of the work (content hash of every input the result depends on). Re-enqueueing the same key returns the same job while it runs; a failed key revives as a fresh attempt.
  - `kind` string, required: Job family, e.g. `verify` | `preview` | `brand_kit_extract`.
  - `status` 'running' | 'succeeded' | 'cancelled', required: running on a fresh/deduped enqueue; succeeded when an identical request already completed. Cancelled is part of the shared status vocabulary; identical cancelled work revives to running.

## Other responses

- `400`: Bad request — validation_error / invalid_cursor / invalid_query.
- `401`: Missing or invalid Authorization header.
- `402`: Payment required — billing_required or x402 payment_*.
- `403`: Forbidden — permission insufficient.
- `404`: Resource not found.
- `409`: Conflict — stale_etag / idempotency_conflict / suggestions_already_complete.
- `422`: Unprocessable — invalid_composition / media_in_use / session_in_use / render_not_cancellable.
- `500`: Internal error.

---

[API](https://skmtc.dev/cueframe/apis/cueframe-api.md) · [All operations](https://skmtc.dev/cueframe/apis/cueframe-api/llms.txt) · [OpenAPI document](https://skmtc.dev/cueframe/apis/cueframe-api/revisions/8f9bfe362b4a?raw)
