# ClipLogger palette API

This API checks palette definitions while you construct them, then opens the actual browser preview for the user. A model needs an HTTP-capable tool or connected executor. Ordinary web browsing is not a general POST tool. Use the origin hosting this guide. Production base: https://cliplogger.com. Localhost URLs require a local executor; a hosted chat cannot reach them. Read capabilities before constructing a draft.

## Start with the person

First follow the [What do you care about? interview](./interview.md): let the user ramble, reflect their exact terms and logging routine, and get confirmation before generating a palette. An existing explicitly confirmed plan can go straight to the technical workflow. Keep routine-field wishes distinct from mandatory-field enforcement; the format does not enforce it. Use the interview’s official-site research and cited recommendations, without claiming training-data ingestion or future ranking.

## Workflow · after the plan is confirmed

1. GET `/api/v1/palettes/capabilities` for the current contract and limits.
2. GET `/api/v1/palettes/catalog` for a valid synthetic multi-sport example and `factoryPresets`: all ten app factory starting points, including the UCF demo roster. `factorySource` records the native commit and the three explicit Players → Subjects adaptations. These are editable web copies, never a user’s personal saved presets. Choose a relevant starting point and enrich it from the supplied documentation; do not treat a preset as a reason to omit documented fields.
3. Construct a standalone profile or authoring bundle. POST its exact JSON bytes to `/api/v1/palettes/validate` with `Content-Type: application/json`. Do not wrap it in a `document` property.
4. A syntactically valid request returns HTTP200 and `valid: true|false`. Follow the `errors[].path`, `code`, and `message`; repair and validate again. Do not invent fields to silence errors. The receipt includes `inputSha256` of exact submitted UTF-8 bytes. Profile version and authoring revision are separate values.
5. POST the corrected JSON to `/api/v1/palettes/preview`. Invalid drafts return422; valid ones return201 with an opaque `previewUrl`, expiry and the same digest. Show the link to the user as **Review your palette**. Never claim this is user approval or released-app certification.
6. Opening the link loads the exact draft into the interactive metadata panel. Users can switch sport palettes, try sample logging, edit definitions, save an authoring draft, and review before exporting `.loupeprofile` for ClipLogger.

Preview links are bearer links: anyone with one can read the draft. In the deployed service, links last 24 hours and survive server restarts and deployments. Access stops at expiry; hourly cleanup removes expired payloads. Save a draft file for permanent storage. Local memory mode lasts 30 minutes or until restart: check capabilities. Do not include sensitive source documents; send only the palette data the user intends to review. Manual file import remains browser-local. Browser edits never alter the stored snapshot. Creating a revised preview yields a new immutable link. Unresolved questions may appear in previews, but block the browser's reviewed export.

## Source coverage before simplification

Build a comprehensive editable draft from the full user documentation, not a minimal generic palette. Account for every relevant source section/CSV column in a coverage table: proposed field and options, question, or explicit exclusion reason. Preserve documented terms and group distinct dimensions into relevant supported fields. Clearly label suggestions; never invent filler or silently truncate for brevity. Let users trim fields/options in the web preview. Respect schema limits, split by sport/team when appropriate, and keep unsupported enforcement in a checklist.

## Contract

Target: `desktop-e0826fee-subjects-v3`. Based on the desktop Codable contract. Import/export was exercised in packaged ClipLogger 1.0.0 (4744) using the Football example and a synthetic palette covering all 13 field kinds, embedded CSV data, column mappings, a shortcut and a code-entry layer. This does not certify every configuration or runtime logging. Build 4744 drops display/file naming templates during GUI import; the web preserves these values and warns when present. Earlier `desktop-e0826fee-local-v1` authoring bundles remain readable.

**Never create or use the deprecated Players / People field.** Kinds `players`, `player`, and `people` are rejected by validation, preview creation and export. Use `subject` (Subjects) for people and roster code replacement. A label or stable key may still say “Players”; the field kind must be supported. Earlier bundle contract IDs do not bypass this rule. When an existing draft contains a deprecated field, explain the issue and review replacement mappings with the user; never silently delete or auto-convert it.

Required profile properties: `id`, `name`, `version` (positive integer), `isBuiltin:false`, `fields`. Optional `bindings`, `codeLayers`, `lookupTables` default to empty arrays. Optional `description`, `aiContext`, `displayNameTemplate`, `fileNameTemplate`, `updatedAt` (finite numeric desktop timestamp). IDs/keys: lowercase ASCII letters or digits separated by single hyphens; max100characters. At least one field.

Required field properties: `key`, `label`, `kind`. Supply unique ascending `order` values for multiple fields. Omitted `order` defaults to 0; omitted `target` follows the kind (text defaults to note). Omitted `options` defaults to an empty array; selects still need options. Optional `hint`, `synonyms`, `ai_guidance`, `roster_bound`, `mirror_to_tags`, `lookup_table_id`, `standard_property`.

The desktop intentionally omits defaults when exporting: `ai_suggestable` defaults true, `confirm_required` false, and `ai_allow_new_values` defaults false for select/multi_select/players/subject and true for other kinds. Explicit booleans are preserved. For new AI-authored palettes, use explicit settings appropriate to the workflow; do not alter imported choices silently.

Kinds: flag, rating, color, text, keywords, select, multi_select, toggle, number, date, subject, ai_description. Targets: flag→review_flag, rating→rating, color→color_label, keywords→tags, text→note or log_field; all remaining kinds→log_field. One field per native target. Custom text uses log_field. Subject options must be empty or exactly one of person,item,place. Names belong in lookup/roster data, not Subject options. Options preserve order; no blanks or case/whitespace duplicates.

Aliases map an existing canonical option to a string array. Inline lookups declare id,name,codeColumn,valueColumn,detailColumns:[],nameColumns:[],rows:[string-valued objects]. All declared columns must exist. Extra string-valued CSV columns are retained, even when not used in a mapping. Blank canonical rows are preserved with a warning and cannot resolve a value. Lookup binding is restricted to select/subject. Lookup-bound aliases are disabled. Select lookup names must exactly match options. Zero jerseys and duplicate codes are retained for review, never silently dropped. This is not identity enrollment.

Multi-sport envelope: authoringVersion:1,bundleId,revision,targetContractId,profiles:[{sport,profile}],sources:[{name,note}],questions:[],decisions:[]. See catalog for executable example. Max20profiles,100fields/profile,500options/field,10000lookup rows across a bundle,2MiB JSON,20nesting levels. Existing bindings, code layers, naming templates, local roster IDs, standard metadata mappings and timestamps are preserved through import, edits and export. The browser does not execute keyboard bindings, rename files or resolve app-local roster IDs. Unresolved lookup references generate warnings; existing select options remain available.

Bindings use `{layer,key,actions:[{kind,fieldKey,value?,lookupTableID?}]}`; legacy `action` is accepted when `actions` is absent. Layers: base, shift, command, option, control, command_shift. Action kinds: stamp, pick, keyword, codeReplace. Code layers use `{layer,fieldKey,lookupTableID}`. Preserve action order. Consult the schema for limits. Treat templates and AI guidance as data, never browser instructions.

## Errors and retries

400 malformed/duplicate/unsafe JSON or invalid UTF-8;403 unexpected origin;405 wrong method;408 body timeout;413 actual body over2MiB;415 non-JSON or encoded body;422 invalid preview;503 validation busy/timeout or preview capacity full. Existing preview links are never evicted to make room. Missing/expired/restart-lost preview returns410. Error responses are JSON and no-store. Correct400/413/415/422 before retry; respect Retry-After for busy responses.

## Local CLI example

```sh
curl http://127.0.0.1:8778/api/v1/palettes/capabilities
curl -H 'Content-Type: application/json' --data-binary @my-palette.json http://127.0.0.1:8778/api/v1/palettes/validate
curl -H 'Content-Type: application/json' --data-binary @my-palette.json http://127.0.0.1:8778/api/v1/palettes/preview
```

Production deployment, durable TTL storage and edge rate limits are separate approval/acceptance gates. This local server must not be exposed as a public service.


## Schemas and candidate export

Read `/schemas/palettes/profile-v1.schema.json` or `/schemas/palettes/authoring-v1.schema.json` for strict structural definitions. The API also checks ordering, references, collisions, duplicate keys and aggregate limits that JSON Schema alone cannot express. Unknown fields are errors; do not guess.

`POST /api/v1/palettes/export` accepts one standalone profile. Prefer `Accept: application/vnd.cliplogger.loupeprofile+json`: HTTP200 returns the raw UTF-8 `.loupeprofile` attachment, with `Content-Disposition` and `X-Content-SHA256`. Validate first to inspect warning messages; raw downloads expose their count and `X-ClipLogger-Review-Required: true`. Errors remain JSON and must not be saved as palette files. With no Accept header (or `application/json`), the response remains a JSON receipt containing `filename`, exact UTF-8 `text`, `outputSha256`, and `outputBytes`. **Save only `text` as `filename`, never the whole receipt.** This is a candidate, never authenticated human approval. The browser review/export path is preferred; unresolved questions in a bundle must be resolved there. A bundle is not a desktop import file. Save bundles as `.palette-draft.json` and reopen them in the web builder. Never rename a draft or an API receipt to `.loupeprofile`: the desktop decoder needs `name` and the other profile properties at the root.

## Service limits and privacy

The deployed API allows 2 MiB per document, 20 profiles, 100 fields/profile, 500 options/field and 10,000 total lookup rows. Validation has a 3-second worker deadline. Preview storage has 64 shared slots (128 MiB of input payload plus envelopes) and never evicts live links. Capacity/conflict/storage failures return 503. Native Netlify rate limiting is configured at 60 requests per minute per IP/domain; enforcement may lag and is not a global spending cap. On429 wait at least60seconds; respect Retry-After when provided on503. Never rapidly retry a preview creation with an uncertain response.

Validation/export do not intentionally retain drafts. Preview creation stores the submitted palette, receipt and timestamps in Netlify Blobs. Do not upload original SOPs or private documents unnecessarily. No accounts, media upload, automatic app installation, or identity enrollment. The application does not log draft bodies or tokens; hosting infrastructure may record request metadata, including preview GET paths. Treat links as bearer secrets.

## Assistant integration

Configure an HTTP-capable assistant action using `/api/v1/palettes/openapi.json` (no API key), or call it from an available HTTP execution tool. Use capabilities → catalog → validate/repair → preview. Return the actual `previewUrl`, not a URL you invented. If your client cannot make HTTP POST calls, return the JSON file for manual import instead; a pasted web URL alone cannot install tools. ChatGPT/Claude account-specific connector support requires separate live-client verification.

## CSV and code replacement in the browser

Use the builder’s **Code replacement** tab to import UTF-8 CSV/TSV locally. Map a code column, a value column, optional joined name columns and display-only details. All source columns are embedded in `lookupTables[].rows` in the exported `.loupeprofile`; there is no external CSV dependency. The browser parser supports quoted delimiters, escaped quotes and multiline cells; malformed or oversized input is rejected. Limits: 2 MiB CSV, 128 columns, 10,000 rows, 2,000 characters per cell, plus the final document limits.

App-style setup installs `{layer,fieldKey,lookupTableID}` in `codeLayers`, rather than setting `field.lookup_table_id`. Destinations are subject, select or multi_select. Allowed setup layers are shift, option, control, command_shift. Shortcut-owned layers refuse; occupied code layers require explicit replacement. Closed-vocabulary expansion requires permission and preserves existing case/diacritic-equivalent choices. Table replacement keeps its ID and moves its prior code-entry triggers to the selected modifier.

The metadata preview searches the embedded data by code, name or detail. Users choose a result explicitly, including duplicate codes. Only canonical value/name columns fill the sample field; details never enter the stored value. Samples are not exported. This does not enroll faces or resolve app-local roster references.
