Check split sheets and metadata
A list of discrepancies (bad splits, missing co-writers or publishers, wrong IDs), each citing its source lines, and a credit sheet to confirm.
Built on: Typed judgment, Grounding, Signed record
Loading the tool…
Use it your way
Use it from your codeThe hosted API with your key, and prompts to paste into a coding agent
Get an API key
- Call the split-sheet and metadata checker API from your own code in minutes.
- Every model answer carries a signed receipt.
- Nothing to install; we run the models.
Run it yourself, on request
- The same open models and app, on 1× RTX PRO 6000 (96 GB) or 1× RTX 5090 (32 GB) for the model; parsing, OCR and every check run on CPU.
- Data never leaves your machines, and there are no Decosa charges.
- One prompt for Claude Code or Codex assembles the whole stack.
- Early access: the container images are not public yet and the source needs access; the prompt says how to ask.
Build with it
Paste one of these into Claude Code, Codex or another coding agent. The first wires your project to the hosted API with your DECOSA_API_KEY. The second pulls our containers and runs the same stack on your own GPU, with no Decosa charges.
- Base URL
- https://api.decosa.ai
- Auth
Authorization: Bearer $DECOSA_API_KEY(or a demo session token)- Tool id
- split-sheet-check
Use the hosted API
# Decosa Split-sheet and metadata checker: use the hosted API
You are wiring Decosa's split-sheet and metadata checker into this project. It takes a release's documents (split
sheets as text, PDF, DOCX or scans; the release metadata as a DDEX ERN message or a distributor CSV; publishing
contract excerpts; society registration exports as CSV) and returns every discrepancy with the lines it came from:
splits that do not add up to 100%, co-writers missing from a source, role and society mismatches, missing publishers,
and ISRC, ISWC, IPI and UPC format or check-digit errors. It proposes one credit sheet, which a person confirms, and
signs records of the check and the confirmation. Each model call has its own signed receipt. Use only what is listed
below. If you need something else, stop and ask me.
- Base URL: `https://api.decosa.ai`
- Health check: `GET https://api.decosa.ai/healthz`.
- The hosted API is for released metadata, test catalogs and integration testing. Unreleased catalogs and contract
terms are confidential: for those use the self-host prompt instead.
- It is a checking aid, not legal or accounting advice. Show every result as something to check.
## Auth: API key (or a demo session)
1. Preferred: an API key (`dk_…`) from "Get an API key" on the tool page. Keep it in an environment variable,
`DECOSA_API_KEY`, never in code. Send `Authorization: Bearer $DECOSA_API_KEY`.
2. Without a key: `POST https://api.decosa.ai/demo/session` with `{"vertical": "split-sheet-check"}` returns `{"token", "expires_at", "budget"}`.
a limited number of sessions per network per hour (the current limits are in `demo_sessions` of GET /healthz); each session has a token allowance (its `budget`) (a release of three to five split sheets uses about
1,500). Over a limit: HTTP 429 with `Retry-After`.
3. One check at a time per demo token (409 otherwise).
## Endpoints
- `POST /splits/check` (token). Body:
`{"documents": [{"name": "...", "kind": "split_sheet" | "release" | "contract" | "registration", "text": "..."} | {"name", "kind", "data_b64": "<base64 of a PDF, DOCX, PNG or JPEG>"}], "extract"?: true, "stream"?: true}`,
or `{"sample": "harbor-lights-planted"}` (see `GET /splits/samples`).
- Release metadata and registrations must be DDEX XML or CSV text (headers are matched loosely: title, writer or first
and last name, role, IPI or CAE, society or PRO, share, publisher). Split sheets and contracts can be anything; the
model reads them and every value it reads must be found in the document, or it is not used.
- Limits: 12 documents, 8 of them split sheets or contracts, 4 MB per file, 60,000 characters per document.
- With `"stream": true` (or `Accept: text/event-stream`) it streams `intake`, `parsed` or `extracted` per document
(every value with its line and quote), `receipt` after each model call, `judgment` per same-writer or role call,
then `report`, `done` and `budget`. With `"stream": false`: one JSON object `{run_id, sources, receipts, report, budget}`.
- `report.issues`: `[{id, kind, severity: error|warning, work, writer, message, sources: [{src, name, line, where, quote}]}]`.
Kinds: `share_total`, `share_mismatch`, `missing_writer`, `role_mismatch`, `id_invalid`, `id_mismatch`,
`pro_mismatch`, `publisher_missing`, `publisher_mismatch`, `share_unreadable`, `work_missing`.
- `report.proposal`: per song, per writer, each field `{value, status: agreed|chosen|invalid|missing, from, alternatives, rule}`.
- `POST /splits/parse` (token): the same body, code only (no split sheets or contracts read, no budget), always JSON.
- `POST /splits/ids` (token) `{"values": ["T-034.524.680-1", "00000000199"]}`: identifier checks only.
- `POST /splits/runs/{run_id}/confirm` `{"reviewer": "...", "edits": [{"work": "W1", "writer": 0, "field": "share", "value": "33 1/3"}], "accept": true}`:
422 lists what still blocks signing (shares not adding to 100%, a bad IPI, a field the sources disagree on that was
neither edited nor accepted); 200 returns the signed credit sheet.
- `GET /splits/runs/{run_id}/export?format=csv` (the proposed or confirmed sheet), `md` (report), `record` (signed check), `sheet` (signed confirmed sheet).
- `POST /record/verify` (no token) `{"record": {...}}` → `{ok, summary, bad}`.
- `GET /splits/info`, `GET /splits/samples`, `GET /splits/samples/{id}`, `GET /attest/signing-key` (no token).
## Example: check a release before delivery (Python, `pip install httpx`)
```python
import httpx, os, pathlib
API = "https://api.decosa.ai"
H = {"Authorization": f"Bearer {os.environ['DECOSA_API_KEY']}"}
docs = [{"name": p.name, "kind": "split_sheet", "text": p.read_text()} for p in pathlib.Path("split-sheets").glob("*.txt")]
docs.append({"name": "release.xml", "kind": "release", "text": pathlib.Path("release.xml").read_text()})
docs.append({"name": "mlc-export.csv", "kind": "registration", "text": pathlib.Path("mlc-export.csv").read_text()})
r = httpx.post(f"{API}/splits/check", headers=H, timeout=600, json={"documents": docs, "stream": False})
r.raise_for_status()
run = r.json()
for x in run["report"]["issues"]:
print(x["severity"], x["kind"], x["message"], [(s["src"], s["line"]) for s in x["sources"]])
```
## Honest limits
- Measured on synthetic catalogs only (fictional songs and writers); see the numbers on the Stack tab.
- Scans are read with Tesseract OCR: handwriting-style scans lose identifiers often, and an OCR misread is marked as
such rather than trusted.
- Registration and distributor CSVs are matched by header names; an unusual layout may need its columns renamed.
Run it yourself (containers)
On request. The container images and the compose file aren’t public yet. Ask for self-host access and Decosa sends the registry (DECOSA_REGISTRY) and the compose file’s URL (DECOSA_COMPOSE_URL) these steps use. They are the steps we tested end to end on a fresh machine.
# Decosa Split-sheet and metadata checker: run it yourself (containers)
You are setting up the Decosa split-sheet and metadata checker on this machine, so unreleased catalogs and contract
terms never leave it. It reconciles split sheets, release metadata (DDEX or CSV), contract excerpts and society
registrations, checks every identifier and share sum in code, proposes a credit sheet for a person to confirm, and
signs records. Nothing is sent to Decosa's hosted API.
Status: the container images (${DECOSA_REGISTRY}/decosa-*) and the compose file are on request while self-host is in early access (not on a public registry yet): ask at https://decosa.ai/contact?topic=self-host, and Decosa sends the registry as DECOSA_REGISTRY, the compose file URL as DECOSA_COMPOSE_URL, and pull access. If a pull fails with
"not found", "denied" or "unauthorized", stop and tell me. Do not substitute other images.
Ask me before any command that needs sudo, and show me the command first.
## Step 0: set up with a coding agent, rehearse on mock data, then go private
This prompt is for a coding agent running on the machine that will host the service. We recommend Claude Code with
Claude Opus 5.5; any capable coding agent works. Work in this order:
1. Set up on mock data only. During the whole setup you (the agent) work with the synthetic sample bundle below and
nothing else. Do not ask me for real data, and do not open, read, list or copy files that hold real data, even to
"test with something realistic".
2. Rehearse. When the steps below are done and the service is healthy, fetch the mock-data bundle for this tool,
https://decosa.ai/samples/split-sheet-check.zip (124 KB, 12 checks, synthetic or openly licensed: see `licence` in expected.json),
show me what is in it, and run the rehearsal against the local API:
`docker compose exec api python scripts/rehearse.py split-sheet-check` (the api image carries the same bundle under /app/rehearsal/split-sheet-check/;
with no key set, the script asks the local API for a short demo token). From a decosa-api checkout instead:
`python scripts/rehearse.py split-sheet-check --bundle split-sheet-check.zip --base-url http://127.0.0.1:<PORT>`.
It sends the mock inputs to the local API and prints PASS or FAIL for each expected property (for example: "all seven documents are read", "the 105% split is found", "the wrong ISWC check digit is found"). Show me
the full output. Every check must pass. If one fails, fix the install and run it again; never edit `expected.json`
to make a check pass.
3. Stop there. Once the rehearsal passes, tell me, and I will run my own data against the local API myself, on this
machine.
For the person running this: a coding agent that runs in the cloud sees everything in its context, including files it
reads, command output and anything pasted into the chat. Keep real data out of the chat and out of anything the agent
can read. Switch to your own data only after the rehearsal has passed and the agent's work is done.
## Steps
1. Docker: if `docker compose version` fails, install Docker Engine and the compose plugin using Docker's official
instructions for this distribution (docs.docker.com/engine/install). Also install the NVIDIA container toolkit and
check `docker run --rm --gpus all nvidia/cuda:12.8.0-base-ubuntu24.04 nvidia-smi`.
2. Fetch the compose file:
`mkdir -p ~/decosa && cd ~/decosa && curl -fsSL "${DECOSA_COMPOSE_URL}" -o compose.yaml`
Read it. Keep the `llm` service (Qwen3.8-27B on vLLM) and the `api` service. For the `api` service set
`DECOSA_LLM_ROUTE=direct`, `DECOSA_LLM_URL=http://llm:8000/v1`, `DECOSA_LLM_MODEL=qwen3.8-27b`,
`DECOSA_SPLITS_MAX_DOCUMENTS=40`, and bind every port to 127.0.0.1. The api image includes Tesseract for scans.
3. Pull and start: `docker compose pull && docker compose up -d`. Wait for the `llm` health check (the first start
downloads about 20 GB of weights).
4. Check: `curl -fsS http://127.0.0.1:<PORT>/splits/info` shows `"route": "direct"` and `"ocr": true`;
`GET /attest/signing-key` shows this box's public key. Show me the key: it is what others pin to verify my records.
5. Smoke test: get a token with `POST /demo/session {"vertical":"split-sheet-check"}` and run
`POST /splits/check {"sample": "harbor-lights-planted", "stream": false}`. Expect every document read, and issues
for a 105% split, an ISWC check digit, a missing co-writer, a composer/lyricist swap, a society mismatch and a
missing publisher, each with its source lines; every receipt `attested`. Then `POST /record/verify` with
`report.record`: `ok` must be true.
6. Report back: the public key and key id, the totals, the issues and how long the run took.
Off, and keep it off on a box that holds unreleased catalogs or contracts: joining serves other people's requests on
this GPU. If I ask for it later, on a separate machine, follow the Provide page instead of improvising.
No NVIDIA GPU? This tool also runs entirely on an Apple Silicon Mac (MLX, 32 GB of unified memory or more): use https://decosa.ai/prompts/split-sheet-check-mac.md instead.
Run it on your own hardwareWhat it needs, and the prompt that sets it up
Run it on your own GPU
Same app, same pinned models, your hardware. Nothing goes to our servers and there are no Decosa charges.
Hardware check
Check your own hardware- CPU only, 64 GB RAMlite tierRuns with a smaller tier
The standard tier does not fit: Qwen3.8-27B (NVFP4) needs a GPU. The lite tier fits.
- GeForce RTX 4090standard tierRuns
The standard tier fits with changes: Replace Qwen3.8-27B (NVFP4) with A community 4-bit build of Qwen3.8-27B (AWQ or GGUF). This build is NVIDIA NVFP4, which needs a Blackwell GPU. (Memory is an estimate.)
- GeForce RTX 5090standard tierRuns
The standard tier fits with changes: Qwen3.8-27B (NVFP4): run it at its smallest setting (about 28 GB instead of 57.6 GB), with a shorter context and fewer parallel sessions.
- 2x GeForce RTX 5090standard tierRuns
The standard tier fits with changes: Split the language model across the GPUs with tensor parallelism (vLLM --tensor-parallel-size).
- L40Sstandard tierRuns
The standard tier fits with changes: Replace Qwen3.8-27B (NVFP4) with Qwen3.8-27B official FP8. This build is NVIDIA NVFP4, which needs a Blackwell GPU.
- H100 80 GB (SXM)standard tierRuns
The standard tier fits with changes: Replace Qwen3.8-27B (NVFP4) with Qwen3.8-27B official FP8. This build is NVIDIA NVFP4, which needs a Blackwell GPU.
- RTX PRO 6000 Blackwell 96 GBstandard tierRuns
The standard tier fits (57.6 of 96 GB).
- 2x RTX PRO 6000 Blackwell 96 GBstandard tierRuns
The standard tier fits (57.6 of 192 GB).
- Apple M3 Ultra (Mac Studio), 96 GBstandard tierRuns
The standard tier fits (32 of 96 GB).
- Apple M5 Max, 64 GBstandard tierRuns
The standard tier fits (32 of 64 GB).
Memory per component comes from measured footprints, the tool's stack.json, or an estimate from its parameter count, and each is labelled that way below. Only an RTX PRO 6000 and an M3 Ultra Mac Studio have actually been run.
On request. The container images and the compose file aren’t public yet. Ask for self-host access and Decosa sends the registry (DECOSA_REGISTRY) and the compose file’s URL (DECOSA_COMPOSE_URL) these steps use. They are the steps we tested end to end on a fresh machine.
- 1
Check the GPU, Docker and the NVIDIA Container Toolkit
The driver must see the GPU, and Docker must be able to pass it into a container.
nvidia-smi docker compose version docker run --rm --gpus all ubuntu nvidia-smi
- 2
Fetch the compose file
One file describes the API and the language model as services.
mkdir -p ~/decosa && cd ~/decosa curl -fsSL "${DECOSA_COMPOSE_URL}" -o compose.yaml - 3
Pull and start
The first start downloads pinned model weights, tens of gigabytes.
docker compose pull docker compose up -d
- 4
Check health
Wait until the API reports ok with the language model loaded. Then point your app at the local base URL.
curl -fsS http://localhost:<PORT>/healthz # {"ok": true, "llm": true, ...} curl -fsS -X POST http://localhost:<PORT>/demo/session \ -H 'Content-Type: application/json' -d '{"vertical":"split-sheet-check"}'
Set up with a coding agent, rehearse on mock data, then go private
- Set up with a coding agent. Paste the self-host prompt into a coding agent on the machine that will run the service. We recommend Claude Code with Claude Opus 5.5; any capable coding agent works.
- Rehearse on mock data. The agent runs the tool on a bundle of synthetic inputs and checks each answer against the bundle's
expected.json. Every check must print PASS. - Go private. Only then do you run your own data against the local API, yourself, on that machine. Never give the agent real data during setup: a coding agent that runs in the cloud sees everything in its context, so keep real data out of the chat and out of the files it reads.
docker compose exec api python scripts/rehearse.py split-sheet-check
Download the mock-data bundle (124 KB, 12 checks)expected.json
A fictional three-song EP: three split sheets, a DDEX release file, a society registration export and two co-publishing contract excerpts, with planted errors. The check must read every document and find the 105% split, the wrong ISWC check digit, the co-writer missing from a source and the society mismatch, and end in a signed record that verifies. A second, smaller catalog adds a scanned split sheet (PDF, read by OCR) and a distributor CSV with a wrong UPC check digit.
What the rehearsal checks
- all seven documents are read
- the 105% split is found
- the wrong ISWC check digit is found
- the co-writer missing from a source (Beatriz Thibodeaux) is found
- the society mismatch (Tobias Okafor) is found
- the composer/lyricist swap (Junie Castellanos) is found
- the scanned split sheet is read by OCR: its writers are found
- the wrong UPC check digit in the distributor CSV is found
- the wrong IPI check digit is found
- the signed record verifies
- a record with its error count changed no longer verifies
- every model call has a signed receipt
Licence: Fictional catalog generated by scripts/splits_data.py (seeded): names, songs, societies' registrations and identifiers are invented. Part of decosa-api, AGPL-3.0-or-later.
Prompt for your coding agent
# Decosa Split-sheet and metadata checker: run it yourself (containers)
You are setting up the Decosa split-sheet and metadata checker on this machine, so unreleased catalogs and contract
terms never leave it. It reconciles split sheets, release metadata (DDEX or CSV), contract excerpts and society
registrations, checks every identifier and share sum in code, proposes a credit sheet for a person to confirm, and
signs records. Nothing is sent to Decosa's hosted API.
Status: the container images (${DECOSA_REGISTRY}/decosa-*) and the compose file are on request while self-host is in early access (not on a public registry yet): ask at https://decosa.ai/contact?topic=self-host, and Decosa sends the registry as DECOSA_REGISTRY, the compose file URL as DECOSA_COMPOSE_URL, and pull access. If a pull fails with
"not found", "denied" or "unauthorized", stop and tell me. Do not substitute other images.
Ask me before any command that needs sudo, and show me the command first.
## Step 0: set up with a coding agent, rehearse on mock data, then go private
This prompt is for a coding agent running on the machine that will host the service. We recommend Claude Code with
Claude Opus 5.5; any capable coding agent works. Work in this order:
1. Set up on mock data only. During the whole setup you (the agent) work with the synthetic sample bundle below and
nothing else. Do not ask me for real data, and do not open, read, list or copy files that hold real data, even to
"test with something realistic".
2. Rehearse. When the steps below are done and the service is healthy, fetch the mock-data bundle for this tool,
https://decosa.ai/samples/split-sheet-check.zip (124 KB, 12 checks, synthetic or openly licensed: see `licence` in expected.json),
show me what is in it, and run the rehearsal against the local API:
`docker compose exec api python scripts/rehearse.py split-sheet-check` (the api image carries the same bundle under /app/rehearsal/split-sheet-check/;
with no key set, the script asks the local API for a short demo token). From a decosa-api checkout instead:
`python scripts/rehearse.py split-sheet-check --bundle split-sheet-check.zip --base-url http://127.0.0.1:<PORT>`.
It sends the mock inputs to the local API and prints PASS or FAIL for each expected property (for example: "all seven documents are read", "the 105% split is found", "the wrong ISWC check digit is found"). Show me
the full output. Every check must pass. If one fails, fix the install and run it again; never edit `expected.json`
to make a check pass.
3. Stop there. Once the rehearsal passes, tell me, and I will run my own data against the local API myself, on this
machine.
For the person running this: a coding agent that runs in the cloud sees everything in its context, including files it
reads, command output and anything pasted into the chat. Keep real data out of the chat and out of anything the agent
can read. Switch to your own data only after the rehearsal has passed and the agent's work is done.
## Steps
1. Docker: if `docker compose version` fails, install Docker Engine and the compose plugin using Docker's official
instructions for this distribution (docs.docker.com/engine/install). Also install the NVIDIA container toolkit and
check `docker run --rm --gpus all nvidia/cuda:12.8.0-base-ubuntu24.04 nvidia-smi`.
2. Fetch the compose file:
`mkdir -p ~/decosa && cd ~/decosa && curl -fsSL "${DECOSA_COMPOSE_URL}" -o compose.yaml`
Read it. Keep the `llm` service (Qwen3.8-27B on vLLM) and the `api` service. For the `api` service set
`DECOSA_LLM_ROUTE=direct`, `DECOSA_LLM_URL=http://llm:8000/v1`, `DECOSA_LLM_MODEL=qwen3.8-27b`,
`DECOSA_SPLITS_MAX_DOCUMENTS=40`, and bind every port to 127.0.0.1. The api image includes Tesseract for scans.
3. Pull and start: `docker compose pull && docker compose up -d`. Wait for the `llm` health check (the first start
downloads about 20 GB of weights).
4. Check: `curl -fsS http://127.0.0.1:<PORT>/splits/info` shows `"route": "direct"` and `"ocr": true`;
`GET /attest/signing-key` shows this box's public key. Show me the key: it is what others pin to verify my records.
5. Smoke test: get a token with `POST /demo/session {"vertical":"split-sheet-check"}` and run
`POST /splits/check {"sample": "harbor-lights-planted", "stream": false}`. Expect every document read, and issues
for a 105% split, an ISWC check digit, a missing co-writer, a composer/lyricist swap, a society mismatch and a
missing publisher, each with its source lines; every receipt `attested`. Then `POST /record/verify` with
`report.record`: `ok` must be true.
6. Report back: the public key and key id, the totals, the issues and how long the run took.
Off, and keep it off on a box that holds unreleased catalogs or contracts: joining serves other people's requests on
this GPU. If I ask for it later, on a separate machine, follow the Provide page instead of improvising.
No NVIDIA GPU? This tool also runs entirely on an Apple Silicon Mac (MLX, 32 GB of unified memory or more): use https://decosa.ai/prompts/split-sheet-check-mac.md instead.
Help me customise for my hardware
Pick your GPU or Mac, or enter its memory. You get the tier that fits, the model swaps it needs, measured speed where we have it, and a setup prompt with those choices written in.
GeForce RTX 5090: 32 GB GDDR7, 1,792 GB/s, FP8 and NVFP4. NVIDIA product page
RunsSplit-sheet and metadata checker on GeForce RTX 5090: use the Standard · one 96 GB card (measured; hosted demo) tier
The standard tier fits with changes: Qwen3.8-27B (NVFP4): run it at its smallest setting (about 28 GB instead of 57.6 GB), with a shorter context and fewer parallel sessions.
What this tool's stack says about this hardware:
- 1x RTX 5090 32 GB (fits): Qwen3.8-27B NVFP4 needs about 20 GB of weights plus a small KV cache (split sheets are short). Estimate: same model and prompts as the measured card, not run here on a 5090.
Standard · one 96 GB card (measured; hosted demo): what changesuses estimates
- Qwen3.8-27B (NVFP4): run it at its smallest setting (about 28 GB instead of 57.6 GB), with a shorter context and fewer parallel sessions.
Memory per component
- Checker: decosa-api splits module (decosa_api/verticals/splits) with Tesseract OCR. CPU. Runs on CPU (vram_gb 0 in stack.json).
- Model: Qwen3.8-27B (NVFP4). ~57.6 GB (at least ~28 GB), weights 21.4 GB (from stack.json). Qwen3.8-27B NVFP4: Weights 19.9 GiB (21.4 GB), measured (field stack.json). The compose file gives the server 0.60 of a 96 GB card (57.6 GB) so the rest is FP8 KV cache for several sessions. The 28 GB minimum is an estimate: weights plus a short-context KV cache, which is why several stacks list a 32 GB RTX 5090 as 'estimate'. (stack.json lists 20 GB for this component.)
Expected speed
Not measured.
Not measured on this hardware. The only measured setups are an RTX PRO 6000 Blackwell and a Mac Studio M3 Ultra.
Setup prompt for this hardware
The self-host prompt for Split-sheet and metadata checker, with a hardware plan for GeForce RTX 5090 added after Step 0. Loading the full prompt; until then it points your agent at the prompt's URL.
# Set up Split-sheet and metadata checker on my hardware Fetch https://decosa.ai/prompts/split-sheet-check-selfhost.md and follow it (including Step 0: rehearse on mock data first), with the hardware plan below applied. ## Hardware plan for this machine (from https://decosa.ai/self-host/hardware?use=split-sheet-check) Target machine: GeForce RTX 5090 (32 GB of GPU memory; CUDA, FP8 and NVFP4). Quality tier: Standard · one 96 GB card (measured; hosted demo) (standard). Fit check: runs with changes, about 28 GB of 32 GB used; some memory numbers are estimates, not measurements. First, check the machine: run `nvidia-smi` (or `rocm-smi`, or `sysctl hw.memsize` on a Mac) and confirm the GPUs and free memory match the line above. If they do not, stop and tell me before pulling anything. Use these components (the setup below describes the standard tier; change it to match): - Checker: decosa-api splits module (decosa_api/verticals/splits) with Tesseract OCR, CPU - Model: Qwen3.8-27B (NVFP4) (nvidia/Qwen3.8-27B-NVFP4), 57.6 GB. Change: Qwen3.8-27B (NVFP4): run it at its smallest setting (about 28 GB instead of 57.6 GB), with a shorter context and fewer parallel sessions. GPU placement (set each service's device and its vLLM --gpu-memory-utilization to about the share shown): - GPU 0: Qwen3.8-27B (NVFP4) ~28 GB (88%); about 4 GB left During the rehearsal, watch GPU memory. If a model fails to load or runs out of memory, lower its --max-model-len and --max-num-seqs first, then its memory share, and tell me what you changed. The stack's own component list and compose layout: https://decosa.ai/prompts/split-sheet-check-assemble.md
Or on a Mac Studio
No NVIDIA GPU needed: every model this tool uses runs natively on Apple Silicon through MLX. Any M-series Mac with 32 GB of unified memory or more. Measured speeds and what runs where
From a checkout of decosa-api, one command sets up the models and the API: scripts/mac/setup.sh
Mac prompt for your coding agent
# Decosa Split-sheet and metadata checker: run it on this Mac (Apple Silicon, no NVIDIA GPU) You are setting up the Decosa Split-sheet and metadata checker on this Mac, natively on Apple Silicon. The models run on the Mac's GPU through MLX and decosa-api runs from a git checkout with `uv`. Docker is not used for the models, because Docker on macOS cannot reach the GPU. Nothing is sent to Decosa's hosted API. Every model this tool needs runs on the Mac. It needs 32 GB of unified memory or more. Ask me before any command that needs sudo or installs software with Homebrew, and show me the command first. Never stop or kill a process this setup did not start; if a port is taken, pick another one. ## Step 0: set up with a coding agent, rehearse on mock data, then go private This prompt is for a coding agent running on the machine that will host the service. We recommend Claude Code with Claude Opus 5.5; any capable coding agent works. Work in this order: 1. Set up on mock data only. During the whole setup you (the agent) work with the synthetic sample bundle below and nothing else. Do not ask me for real data, and do not open, read, list or copy files that hold real data, even to "test with something realistic". 2. Rehearse. When the steps below are done and the service is healthy, fetch the mock-data bundle for this tool, https://decosa.ai/samples/split-sheet-check.zip (124 KB, 12 checks, synthetic or openly licensed: see `licence` in expected.json), show me what is in it, and run the rehearsal against the local API: `.venv/bin/python scripts/rehearse.py split-sheet-check` in the decosa-api checkout (the key comes from ~/.decosa-mac/api.key). It sends the mock inputs to the local API and prints PASS or FAIL for each expected property (for example: "all seven documents are read", "the 105% split is found", "the wrong ISWC check digit is found"). Show me the full output. Every check must pass. If one fails, fix the install and run it again; never edit `expected.json` to make a check pass. 3. Stop there. Once the rehearsal passes, tell me, and I will run my own data against the local API myself, on this machine. For the person running this: a coding agent that runs in the cloud sees everything in its context, including files it reads, command output and anything pasted into the chat. Keep real data out of the chat and out of anything the agent can read. Switch to your own data only after the rehearsal has passed and the agent's work is done. ## What runs where | Part | On an NVIDIA GPU | On this Mac | Status | |---|---|---|---| | Checker: DDEX and CSV parsing, OCR, identifier check digits, exact share arithmetic, grounding of every model-read value, reconciliation, the proposed sheet and signed records (no model; CPU) | Python on CPU | The same Python module, run with uv | Runs, measured | | Model: reads split sheets and contract excerpts (values copied as written, with line numbers) and answers typed same-writer and role questions | NVFP4 on vLLM 0.29 (Blackwell) | MLX 4-bit (EigenLabs/Qwen3.8-27B-4bit) on mlx_lm.server 0.31.3; oMLX 0.6.1 with MTP as an option | Runs, measured | ## Steps 1. Check the machine: `uname -m` must print `arm64` (an M-series chip; Intel Macs cannot run MLX), and `sysctl -n hw.memsize` should be at least 32 GB for this tool. Check about 30 GB of free disk with `df -h ~`. Show me the chip (`sysctl -n machdep.cpu.brand_string`) and the memory. 2. Tools: `uv --version`. If it is missing, ask me, then `brew install uv`. 3. Code: `git clone <decosa-api source: on request at https://decosa.ai/contact?topic=self-host> ~/decosa-api` (access required) and `cd ~/decosa-api`. Check that `scripts/mac/setup.sh` exists; if it does not, the checkout is too old: stop and tell me. 4. Start everything with one command: `scripts/mac/setup.sh`. It creates `.venv` (decosa-api) and `.venv-mac` (MLX, mlx-lm, mlx-audio), downloads the weights with the Hugging Face CLI (about 16 GB for the language model), starts the model servers and decosa-api on 127.0.0.1, and mints a local API key into `~/.decosa-mac/api.key` (mode 0600). The first run takes a while because of the downloads; later runs reuse them. If a download fails with 401 or 403, ask me for a Hugging Face token and set `HF_TOKEN`. 5. Check health: `scripts/mac/setup.sh status` shows each server, and `curl -fsS http://127.0.0.1:8445/healthz` must report `"llm": true`. `curl -fsS http://127.0.0.1:8445/attest/signing-key` shows this Mac's public key: show it to me, because it is what others pin to check the receipts and records this Mac signs. 6. Smoke test: `.venv/bin/python scripts/mac/bench_usecases.py split-sheet-check`. It runs the tool's own sample end to end against the local API with the local key and prints `ok`, the wall time, the model calls and the receipts. `ok=True` is the pass condition. If it fails, read `~/.decosa-mac/logs/*.log` and tell me what you found. 7. Point the app at it: the API is `http://127.0.0.1:8445` with `Authorization: Bearer $(cat ~/.decosa-mac/api.key)`, the same routes as the hosted API. To stop everything: `scripts/mac/setup.sh stop`. 8. Report back: the chip and memory, the public key, the smoke-test result and its time, and the output of `scripts/mac/setup.sh status`. ## Good to know - Receipts: every model call is signed with this Mac's own Ed25519 key and names the exact MLX weights (`qwen3.8-27b-mlx-4bit` with a hash of the downloaded files). There is no gateway countersignature on a self-hosted Mac. - The weights are a 4-bit MLX build of the same open models, not the NVFP4 build the hosted route and the published evals use. Expect small differences in wording and scores. - Faster drafting: `scripts/mac/setup.sh stop && scripts/mac/setup.sh --engine omlx` serves the model with oMLX and multi-token prediction (about 2x faster for a single long answer, no faster for many parallel calls; typed judgments then use sampling because oMLX returns no log-probabilities). - Measured speeds for a Mac Studio M3 Ultra and the memory each tool needs: https://decosa.ai/mac. Full details: `docs/self-host-mac.md` in the checkout.
The proof
How we tested itEval results and end-to-end checks, hosted and self-hosted, with dates
Verified end to end
Hosted: verified 25 Sep 2026 · measured 25 Sep 2026: · p50 6.7 s · ~$0.003 per run · 5 receipts
Loading the nightly status…
Self-host: verified 25 Sep 2026 · Fresh clone of the branch into a clean directory, docker build of docker/api/Dockerfile (with Tesseract), the api service with a named volume, pointed at the already-running local vLLM (Qwen3.8-27B NVFP4 on 127.0.0.1:8114) through host networking; then torn down.
Measured cost to run: about $0.28 per 100 releases (hosted, 25 Sep 2026). Self-hosting is free: the code is open and the models are open-weight. You pay only for your own hardware and power.
The image builds with OCR on; the planted sample runs end to end on the direct route in 4.5 s (5 attested calls, the same 9 issues as hosted) and the scanned sample in 5.2 s (OCR in the container); the signed record verifies; no names or titles reach the logs. The model server's own startup was not re-verified (no new GPU load).
Known limits (5)
- Measured on synthetic, fictional catalogs only; real exports from each society and distributor have their own layouts.
- Handwriting-style scans read through Tesseract lose identifiers (33.9% of IPIs read correctly); they are marked as OCR and not trusted.
- A valid check digit says an identifier is well formed, not that it is registered to the person named.
- One release at a time (12 documents hosted); no bulk catalog mode and no CWR filing.
- Latency depends on the shared gateway: 30-100 s per release was measured while it was saturated.
How it's builtThe steps, the models and what each one checks
Get an API key
- Call the split-sheet and metadata checker API from your own code in minutes.
- Every model answer carries a signed receipt.
- Nothing to install; we run the models.
Run it yourself, on request
- The same open models and app, on 1× RTX PRO 6000 (96 GB) or 1× RTX 5090 (32 GB) for the model; parsing, OCR and every check run on CPU.
- Data never leaves your machines, and there are no Decosa charges.
- One prompt for Claude Code or Codex assembles the whole stack.
- Early access: the container images are not public yet and the source needs access; the prompt says how to ask.
Split sheets, DDEX or distributor metadata, contracts and PRO or MLC registrations reconciled: every discrepancy with the lines it came from, a credit sheet to confirm, and signed records.
Load a release's documents: split sheets (text, PDF, DOCX, email or a scan), the release metadata as a DDEX ERN message or a distributor CSV, publishing contract excerpts and society registration exports. Code parses the structured files, adds up every split with exact fractions and checks every ISRC, ISWC, IPI and UPC. An open model reads the messy documents, one receipted call each, and every value it reads must be found in the document before it is used; it also answers typed questions code cannot (is "J. Okafor" the same writer as "Jude Okafor"?). The result is a list of discrepancies (splits over or under 100%, missing co-writers, role and society mismatches, missing publishers, bad check digits), each citing the line, CSV row or DDEX path in every source, and a proposed credit sheet that a person edits and confirms. Code re-checks the confirmed sheet before it signs it.
- Deployment
- Hosted or self-host
- Regulatory
- A checking aid for songwriters, managers, publishers and distributors, not legal or accounting advice. Background, checked 25 Sep 2026: a recording carries two copyrights, the sound recording (identified by ISRC, ISO 3901) and the musical work (ISWC, ISO 15707); writers and publishers are identified by CISAC IPI numbers and releases by GS1 UPC/EAN. In the US the performing-rights societies (ASCAP, BMI, SESAC, GMR) pay the writer's and the publisher's share separately. The Orrin G. Hatch-Bob Goodlatte Music Modernization Act (signed 11 Oct 2018) created a blanket mechanical licence for digital services administered by the Mechanical Licensing Collective (The MLC, designated by the Copyright Office in July 2019; the blanket licence has been available since 1 Jan 2021); royalties The MLC cannot match to a registered work are held and, after a statutory holding period, can be distributed by market share to other copyright owners (17 U.S.C. 115(d)(3)(H)-(J)). A valid check digit means an identifier is well formed, not that it is registered to the person named; the writers agree the splits, and a society pays on its own registration. Rules and forms differ by society and territory. Writer names and IPI numbers are personal data and deal terms are confidential: use the hosted demo for released metadata and test catalogs, and self-host for unreleased catalogs and contracts. Model licence: Apache-2.0 (Qwen3.8-27B); OCR: Tesseract (Apache-2.0).
Text description
Four kinds of document go to the checker on your own machine: split sheets (text, PDF, DOCX or scans), release metadata (DDEX ERN XML or a distributor CSV), contract excerpts and registration exports. Code parses the structured files, OCRs scans with Tesseract, checks every ISRC, ISWC, IPI and UPC and adds every split with exact fractions. Qwen3.8-27B (Apache-2.0) reads the split sheets and contracts, one call each, and every value it reads must be found in the document; it also answers typed same-writer and role questions. Code reconciles the sources. Outputs: discrepancies with the lines they came from, a proposed credit sheet a person confirms, and signed records. On the hosted route each model call gets a receipt that our gateway countersigns.
At a glance
- Data retention
- Documents are held in memory for the request; the run (report and proposal) for one hour, for the token or key that made it. Nothing is written to disk; logs carry counts only.
- What leaves the box
- Self-hosted: nothing. Hosted: the documents go to the model through our gateway, which is why the hosted demo is for released metadata and test catalogs.
- Model cost per release
- A fraction of a cent per release on average at the gateway's list price (measured on synthetic releases); GPU time only when self-hosted.
- Input
- Split sheets and contracts as text, PDF, DOCX or scans (PNG, JPEG, image-only PDF); release metadata as DDEX ERN XML or CSV; registrations as CSV. Hosted limits: 12 documents, 8 of them read by the model, 4 MB per file.
- Output
- Discrepancies with the source lines, a proposed credit sheet (CSV) that a person confirms, a Markdown report, and signed records of the check and the confirmed sheet.
Pick the tier for the quality you need
Same app at every tier. What changes is the models, the hardware they need, and whether receipts are signed. Scores are measured with the source named, or marked not measured.
- In the hosted demo
Lite
code only, any CPU
DDEX, distributor and registration files parsed; every identifier and share sum checked; sources compared. Split sheets and contracts are not read (POST /splits/parse or "extract": false).
- Models
- decosa-api splits module (decosa_api/verticals/splits) with Tesseract OCR
- Hardware
- Any CPU
- Quality evidence
- Identifiers and share sums in the reports rechecked by an independent implementation (disagreements)0 of 374 identifiers, 0 of 151 sumsdecosa-api docs/evals/split-sheet-check.md, 2026-09-25; synthetic catalogs from scripts/splits_data.py (fictional), test seed held out
- Planted identifier errors found (ISWC, IPI, UPC check digits; malformed ISRC)ISWC check digit 3/3; IPI check digits 4/4; UPC check digit 5/5; malformed ISRC 6/6decosa-api docs/evals/split-sheet-check.md, 2026-09-25; synthetic catalogs from scripts/splits_data.py (fictional), test seed held out
- Latency
- measured: well under a second per release.
- Verification
- Proof: partialNo model calls, so no receipts; the record is signed by the instance.
- In the hosted demo
Standard
one 96 GB card (measured; hosted demo)
Qwen3.8-27B reads the split sheets and contracts and answers the typed field questions; everything else is the lite tier's code. Also fits a 32 GB card (estimate).
- Models
- decosa-api splits module (decosa_api/verticals/splits) with Tesseract OCR
- Qwen3.8-27B (NVFP4)
- Hardware
- 1x RTX PRO 6000 96 GB
- Quality evidence
- Planted errors found, 30 held-out synthetic catalogs (43 planted)43 of 43 (100.0%): IPI check digits 4/4; malformed ISRC 6/6; ISWC check digit 3/3; missing co-writer 6/6; missing publisher 1/1; society mismatch 5/5; composer/lyricist swap 8/8; 105% split 5/5; UPC check digit 5/5decosa-api docs/evals/split-sheet-check.md, 2026-09-25; synthetic catalogs from scripts/splits_data.py (fictional), test seed held out
- Issues that are a planted error, not a false alarm (knock-on effects of a planted error excluded)64.8%: id_invalid 18/36; missing_writer 6/9; pro_mismatch 5/6; publisher_mismatch 0/2; publisher_missing 2/2; role_mismatch 8/8; share_total 7/7; share_unreadable 0/1decosa-api docs/evals/split-sheet-check.md, 2026-09-25; synthetic catalogs from scripts/splits_data.py (fictional), test seed held out
- Issues that are a planted error, split by whether the catalog has a scanned split sheettyped-only catalogs (15): 19 of 19, no false alarm; catalogs with a scan (15): 27 of 52, every false alarm traces to an OCR misread (22 of the 25 are warnings marked as OCR)decosa-api docs/evals/split-sheet-check.md, 2026-09-25; synthetic catalogs from scripts/splits_data.py (fictional), test seed held out
- Unknown role words ("beat + chords", "wrote the lyrics") resolved by the typed role question10 of 10 correctdecosa-api docs/evals/split-sheet-check.md, 2026-09-25; synthetic catalogs from scripts/splits_data.py (fictional), test seed held out
- False alarms on the 7 clean catalogs3decosa-api docs/evals/split-sheet-check.md, 2026-09-25; synthetic catalogs from scripts/splits_data.py (fictional), test seed held out
- Split-sheet values read correctly, typed sheets (162 writers)name 100.0%, share 100.0%, role 93.8%, ipi 100.0%, pro 100.0%, publisher 100.0%decosa-api docs/evals/split-sheet-check.md, 2026-09-25; synthetic catalogs from scripts/splits_data.py (fictional), test seed held out
- Split-sheet values read correctly, handwriting-style scans through OCR (59 writers)name 98.3%, share 96.6%, role 98.3%, ipi 33.9%, pro 96.6%, publisher 94.9%decosa-api docs/evals/split-sheet-check.md, 2026-09-25; synthetic catalogs from scripts/splits_data.py (fictional), test seed held out; Tesseract 5 on synthetic handwriting fonts, not real handwriting
- Dev catalogs (8, used to fix the prompt and the OCR handling): planted found / precision10 of 10 / 83.3%decosa-api docs/evals/split-sheet-check.md, 2026-09-25; synthetic catalogs from scripts/splits_data.py (fictional), dev seed
- Latency
- measured on our server, shared gateway: seconds per release, up to a couple of minutes at the slowest; a fraction of a cent per release at the gateway's list price.
- Verification
- Proof: strongHosted: every model call has its own gateway-signed receipt, listed in the signed record.
Also runs on
- A vision-language reader for scansQwen2.5-VL-7B-Instructnot servedRead scanned and handwritten split sheets as images instead of through OCR. Page 32 suggests the standard reader's own vision tower (Qwen3.8-27B, same weights) plus a layout model instead of this Jan 2025 7B. Hardware: 1x 96 GB card.
We host these ourselves when needed: small models get more of our own compute unless we detect a shortage, so they need no community providers.
Every model in the stack
| Model | Tiers | Params · VRAM | Verification | Details |
|---|---|---|---|---|
Checker: DDEX and CSV parsing, OCR, identifier check digits, exact share arithmetic, grounding of every model-read value, reconciliation, the proposed sheet and signed records (no model; CPU)decosa-api splits module (decosa_api/verticals/splits) with Tesseract OCR 0 GBProof: partial | LiteStandard | 0 GB | Proof: partial | |
| ||||
Model: reads split sheets and contract excerpts (values copied as written, with line numbers) and answers typed same-writer and role questionsQwen3.8-27B (NVFP4)nvidia/Qwen3.8-27B-NVFP4 on Hugging Face (opens in a new tab) 27.8B · 20 GBProof: strongIn the hosted demo | Standard | 27.8B · 20 GB | Proof: strongIn the hosted demo | |
| ||||
Vision-language model to read scanned or handwritten split sheets directly (alternate)Qwen2.5-VL-7B-InstructQwen/Qwen2.5-VL-7B-Instruct on Hugging Face (opens in a new tab) 8.3BNo proof yetSelf-host only | Alternate | 8.3B | No proof yetSelf-host only | |
| ||||
Tools, services and hardware
Tools
- DDEX ERN 4.3 schema (release-notification.xsd) (opens in a new tab)Published by DDEX for implementers
The demo and eval DDEX messages are built from it and validate against it (lxml, 25 Sep 2026); the parser also reads ERN 3.8 contributor elements.
Cross-check of the IPI (modulo 101) and ISWC check-digit formulas and their test vectors; our code is our own implementation.
- Patrick Hand and Kalam fonts (opens in a new tab)SIL Open Font License 1.1
Draw the handwriting-style scanned split sheets in the synthetic eval set.
- scripts/splits_data.py and scripts/splits_eval.pyApache-2.0
Generate fictional catalogs with planted errors and an answer key; score detection, extraction and arithmetic against a running server.
- POST /record/verifyApache-2.0
Checks the signed check record or confirmed sheet and names the first entry that was changed. The console also verifies it in your browser.
Services
- decosa-api:8445
${DECOSA_REGISTRY}/decosa-api:<tag>GET /splits/info, /splits/samples; POST /splits/check (SSE or JSON), /splits/parse (code only), /splits/ids; POST /splits/runs/{id}/confirm; GET /splits/runs/{id}/export?format=csv|md|record|sheet. Documents stay in memory for the request and runs for one hour, never on disk; logs carry counts only. The image includes Tesseract and poppler.
- vLLM:8114
vllm/vllm-openai@sha256:c2914767605584b6d8f45686b82de173ecc99e781897aa3d0a66dacd72c51ae1Qwen3.8-27B NVFP4 behind our gateway (hosted) or called directly (self-host).
Hardware
- Any CPU (code only) Fits
Measured: parsing a DDEX message and a registration export and every identifier and sum check take well under a second; OCR of a one-page scan takes a few seconds. No GPU.
- 1x RTX 5090 32 GB Fits
Qwen3.8-27B NVFP4 needs about 20 GB of weights plus a small KV cache (split sheets are short). Estimate: same model and prompts as the measured card, not run here on a 5090.
- 1x RTX PRO 6000 Blackwell 96 GB Fits
Measured on our server: the eval and the hosted demo ran on this card, shared with other services the whole time.
Latency per lane
- The planted demo EP (7 documents, 5 model calls), hosted gateway route6.7 s
Measuredmeasured on our server 2026-09-25: 6.6-7.7 s over 3 smoke runs of the planted sample (5 calls, at most 2 in flight) with the gateway quiet; 42-63 s while other workloads saturated it
- A synthetic release (4-8 documents), hosted gateway route, at most 2 calls in flight9.0 s
Measuredmeasured on our server 2026-09-25: median over 30 test catalogs, max 119.0 s (the slow ones ran while other workloads saturated the shared gateway)
Notes
- The model never adds up a share or checks a digit: it copies what the documents say, and code finds every value in the document before using it. Values it cannot find are shown as not found and left out.
- An identifier that fails its check on a scan but matches a valid one in another source is reported as a probable OCR misread, not an error; one with no match is a warning, marked as OCR.
- Registration and distributor CSVs are matched by header names (title, writer or first and last name, role, IPI or CAE, society, share, publisher). An export whose writers and publishers add to one 100% scale (writers 50 + publishers 50) is detected and doubled per side.
- Two spellings of a writer are merged by rule (same IPI, same name) or by a typed yes/no question to the model; every merge is listed, and a low-confidence one is flagged for a person.
- Not included: filing registrations (CWR), bulk catalogs of thousands of works, the societies' own portals, or sound-recording splits (master royalties).
Run this exact stack on your machine
Paste into Claude Code / Codex to assemble this stack locally. The prompt checks your GPU, pulls the pinned models, writes the compose file and runs a smoke test.
# Assemble the Decosa split-sheet and metadata checker on this machine
You are setting up a checker that reconciles a release's credits: split sheets (text, PDF, DOCX or scans), release
metadata (a DDEX ERN message or a distributor CSV), publishing contract excerpts and society registration exports (PRO
or MLC style CSV). It flags splits that do not add up to 100%, co-writers missing from a source, role and society
mismatches, missing publishers, and ISRC / ISWC / IPI / UPC format and check-digit errors, each with the lines it came
from, and proposes one credit sheet for a person to confirm. Work step by step, show me each command before you run
anything with `sudo`, and stop to ask if a check fails.
## Step 0: set up with a coding agent, rehearse on mock data, then go private
This prompt is for a coding agent running on the machine that will host the service. We recommend Claude Code with
Claude Opus 5.5; any capable coding agent works. Work in this order:
1. Set up on mock data only. During the whole setup you (the agent) work with the synthetic sample bundle below and
nothing else. Do not ask me for real data, and do not open, read, list or copy files that hold real data, even to
"test with something realistic".
2. Rehearse. When the steps below are done and the service is healthy, fetch the mock-data bundle for this tool,
https://decosa.ai/samples/split-sheet-check.zip (124 KB, 12 checks, synthetic or openly licensed: see `licence` in expected.json),
show me what is in it, and run the rehearsal against the local API:
`docker compose exec api python scripts/rehearse.py split-sheet-check` (the api image carries the same bundle under /app/rehearsal/split-sheet-check/;
with no key set, the script asks the local API for a short demo token). From a decosa-api checkout instead:
`python scripts/rehearse.py split-sheet-check --bundle split-sheet-check.zip --base-url http://127.0.0.1:<PORT>`.
It sends the mock inputs to the local API and prints PASS or FAIL for each expected property (for example: "all seven documents are read", "the 105% split is found", "the wrong ISWC check digit is found"). Show me
the full output. Every check must pass. If one fails, fix the install and run it again; never edit `expected.json`
to make a check pass.
3. Stop there. Once the rehearsal passes, tell me, and I will run my own data against the local API myself, on this
machine.
For the person running this: a coding agent that runs in the cloud sees everything in its context, including files it
reads, command output and anything pasted into the chat. Keep real data out of the chat and out of anything the agent
can read. Switch to your own data only after the rehearsal has passed and the agent's work is done.
## 0. Ground rules and licences
- Model: Qwen3.8-27B (Apache-2.0) reads the messy documents and answers two kinds of field question (same writer? which
role?). Everything else is code in decosa-api (AGPL-3.0-or-later): parsing, share arithmetic (exact fractions), identifier
check digits, the comparisons. OCR of scans is Tesseract (Apache-2.0), installed in the api image.
- Catalog data, unreleased titles and contract terms stay on this machine. Bind every port to 127.0.0.1. The service
keeps documents in memory for the request and a run for one hour; nothing is written to disk and logs carry counts
only. Keep it that way; do not add request logging.
- Be honest about what it does: a valid check digit means an identifier is well formed, not that it belongs to the
person named; the proposed sheet is a proposal the writers agree, and it is not legal or accounting advice.
## 1. Check the machine
1. `nvidia-smi`: one GPU with at least 32 GB (Qwen3.8-27B NVFP4 is about 20 GB of weights plus KV cache; an RTX PRO
6000 96 GB or an RTX 5090 32 GB both work). Driver 570 or newer. On a card without NVFP4, use the FP8 weights.
2. No GPU? The code-only mode still works: `POST /splits/parse` (or `"extract": false`) parses DDEX, CSV and
registration files and checks every identifier and sum, but does not read split sheets or contracts.
3. `docker --version` and `docker compose version`. If Docker or the NVIDIA container toolkit is missing, install them
from the official Docker and NVIDIA repositories after asking me, then run
`docker run --rm --gpus all nvidia/cuda:12.8.0-base-ubuntu24.04 nvidia-smi`.
4. Disk: about 30 GB free.
## 2. Images and weights
- `${DECOSA_REGISTRY}/decosa-api:<tag>` (**publishing soon**). If the pull fails, build from source:
`git clone <decosa-api source: on request at https://decosa.ai/contact?topic=self-host>` (access required), check out a release that contains
`decosa_api/verticals/splits/`, and `docker build -f docker/api/Dockerfile -t decosa-api:local .` (the image
includes poppler and Tesseract for PDFs and scans). Use `decosa-api:local` as the api image below.
- `vllm/vllm-openai:v0.29.0`; weights `nvidia/Qwen3.8-27B-NVFP4` (or `Qwen/Qwen3.8-27B-FP8`).
## 3. docker-compose.yml
Write this in `~/decosa/splits/`:
```yaml
services:
llm:
image: vllm/vllm-openai:v0.29.0
command: ["--model", "nvidia/Qwen3.8-27B-NVFP4", "--served-model-name", "qwen3.8-27b", "--max-model-len", "32768",
"--enable-prefix-caching"]
ports: ["127.0.0.1:8114:8000"]
volumes: ["~/.cache/huggingface:/root/.cache/huggingface"]
deploy: { resources: { reservations: { devices: [{ driver: nvidia, count: 1, capabilities: [gpu] }] } } }
healthcheck: { test: ["CMD", "curl", "-fs", "http://localhost:8000/v1/models"], interval: 30s, retries: 20 }
api:
image: ${DECOSA_REGISTRY}/decosa-api:<tag>
ports: ["127.0.0.1:8445:8445"]
environment:
DECOSA_HOST: 0.0.0.0
DECOSA_PORT: "8445"
DECOSA_DATA_DIR: /data
DECOSA_LLM_ROUTE: direct
DECOSA_LLM_URL: http://llm:8000/v1
DECOSA_LLM_MODEL: qwen3.8-27b
DECOSA_SPLITS_MAX_DOCUMENTS: "40"
DECOSA_SPLITS_WORKERS: "4"
volumes: ["decosa-data:/data"]
depends_on: { llm: { condition: service_healthy } }
healthcheck: { test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8445/splits/info', timeout=4)"], interval: 30s, retries: 10 }
volumes:
decosa-data:
```
The api keeps its state (keys, receipts, this box's signing key) in the named volume `decosa-data`, not a host folder:
the image runs as an unprivileged user (uid 10001), and a host folder Docker creates is owned by root, which stops the
api with `PermissionError`. Start everything: `docker compose up -d`. On the first start the api creates this box's
Ed25519 key in the volume (`/data/attest/`); back it up with `docker compose cp api:/data/attest ./attest-backup`,
keep it private and never print it. Every model call on the direct route gets a receipt signed with that key (status
`attested`): an attestation by me, the operator, not a proof of computation.
## 4. Smoke test
1. `curl -s localhost:8445/splits/info | jq '{checks, ocr, model: .model.route}'`: eleven checks, `ocr: true`, route
`direct`.
2. Token: `T=$(curl -s -XPOST localhost:8445/demo/session -H 'content-type: application/json' -d '{"vertical":"split-sheet-check"}' | jq -r .token)`.
3. `curl -s -XPOST localhost:8445/splits/check -H "authorization: Bearer $T" -H 'content-type: application/json' -d '{"sample":"harbor-lights-planted","stream":false}' > run.json`.
Expect every document read (`.report.totals.skipped == []`), and among `.report.issues`: a 105% split
(`share_total`), an ISWC check digit (`id_invalid`), a co-writer missing from the registration (`missing_writer`), a
lyricist registered as a composer (`role_mismatch`), a society mismatch and a missing publisher. Every issue lists its
sources with a line and a quote.
4. The same with `"sample":"harbor-lights-clean"` must give no issues; `"copper-moon-scan"` must read the scanned sheet
(`ocr: true` in the intake event when streamed).
5. `jq '{record: .report.record}' run.json | curl -s -XPOST localhost:8445/record/verify -H 'content-type: application/json' -d @-`
must show `ok: true`. Then confirm the sheet:
`curl -s -XPOST localhost:8445/splits/runs/$(jq -r .run_id run.json)/confirm -H "authorization: Bearer $T" -H 'content-type: application/json' -d '{"reviewer":"me"}'`
returns 422 with the fields to change or accept; resend with the fixed shares in `edits` and `"accept": true` and it
returns a signed credit sheet.
6. Time it: on our RTX PRO 6000 the planted sample (five model calls) took about 30-60 s through the shared gateway.
Tell me what you measure.
## 5. Point the app at the local API
Set `NEXT_PUBLIC_DECOSA_API=http://127.0.0.1:8445` in the site's `.env.local`, or call `POST /splits/check` from your
own delivery or registration flow before a release goes out, and keep the signed records with the release.
Contract: `API_CONTRACT.md`, section "Split-sheet and metadata checker".
Off by default. Joining serves other people's requests on this GPU; never do it on a box that holds unreleased
catalogs or contracts. If I ask for it, follow the provider guide at `/provide` on the site, and do not enable it
without my explicit yes.Rules and regulations it checks againstDated, linked to the primary source; not legal advice
Regulation watch
Loading the watch status…
1 law, rule and guidance page cited; 1 watched nightly at the primary source. A change marks this page for a human re-check; nothing is edited automatically. What we cite and how it is watched
Technical detailsModels, where it runs, labels
In short
Last reviewed
- What it is
- A split sheet checker for a release: it reconciles split sheets with the DDEX or distributor metadata, contracts and PRO or MLC registrations, lists every discrepancy with the lines it came from, and proposes a credit sheet a person confirms before code re-checks and signs it.
- Who it's for
- Teams in music.
- Where it runs
- Hosted or self-host; unreleased catalogs and contracts on your own machine
- Key numbers
On 30 synthetic test catalogs, run once after dev was frozen, it found all 43 planted errors. Precision was 19 of 19 on typed split sheets but 27 of 52 on catalogs with a scanned sheet, where every false alarm traced to OCR. The catalogs come from our own generator.
- 43 of 43 Planted errors found (test split, n = 43)
- 64.8% Precision, all catalogs (test split)
- 19 of 19 Precision, typed split sheets only (test split, n = 19)
- 6.7 s Median end-to-end run, hosted (QA sweep 2026-09-25)
- Models
- Qwen3.8-27B
- Where
- Hosted or self-host; unreleased catalogs and contracts on your own machine
- Checks
- Receipt per model call; signed check record and signed confirmed credit sheet
- Industry
- Music
- Output
- Structured data · Signed record or verdict
- Data
- Personal data · Confidential business data
- Hardware
- 1× 96 GB GPU
- Licence
- Permissive (Apache-2.0, MIT)
- Part of
- Decosa Studio: Studio checks
- Runs in
- Decosa hosted · Self-host
- Built from
- Typed judgment · Grounding · Signed record
Questions people ask
What is a split sheet?
The agreement between a song's writers on who wrote the musical work and what share each one owns, usually with each writer's society, IPI number and publisher. Societies and The MLC pay on their own registrations, not on the split sheet, so when the two disagree, money can go to the wrong person or go unmatched. That gap is what this check looks for. Not legal or accounting advice.
What should a split sheet include?
At least each writer's name, role and share of the work, with the shares adding up to 100%, their society and IPI number, and the publisher for each share. Those are the fields this check reconciles against the release metadata and the registrations: shares that don't add up, a missing co-writer, a role or society mismatch, a missing publisher or a bad IPI check digit are flagged.
What split sheet discrepancies does it find?
Splits over or under 100%, missing co-writers, role and society mismatches, missing publishers and bad ISRC, ISWC, IPI or UPC check digits. Each one cites the line, CSV row or DDEX path in every source.
Does the AI add up the splits?
No. The model only copies what messy documents say, and every value must be found in the document before it is used. Code adds up shares with exact fractions and checks every identifier.
How well does it work?
On 30 synthetic, fictional test catalogs it found all 43 planted errors. Precision was 19 of 19 on typed split sheets but 27 of 52 on catalogs with a scanned sheet, where all 25 false alarms traced to OCR.
Does a valid IPI or ISWC mean the registration is right?
No. A valid check digit says an identifier is well formed, not that it is registered to the person named. The writers agree the splits, and a society pays on its own registration.
Does it register works or file CWR?
No. It checks one release at a time (12 documents hosted) and proposes a credit sheet that a person edits and confirms; code re-checks the confirmed sheet before signing it. It does not file CWR or use the societies' portals.
Should unreleased catalogs go to the hosted demo?
No. Writer names and IPI numbers are personal data and deal terms are confidential: use the hosted demo for released metadata and test catalogs, and self-host for unreleased catalogs and contracts.
Ask a question or leave feedbackWe read every message and publish useful answers
Ask about Split-sheet and metadata checker
We read every message. Questions, comments and our answers show here once we have reviewed and approved them.
Loading questions…