Royalty calculation
Send a royalty statement and the contract that governs it; get back who is owed what. The contract can be PDFs that Remediio's AI reads, or terms you already hold.
/oneclick/v1/royalties- Price: 30 credits
- Request: multipart/form-data
- Response: text/event-stream
- Typical time: 2 s – 2 min
Request
Send exactly one of contracts (PDFs, read by Remediio's AI) or contract_terms (structured JSON). Sending both, or neither, is a 422.
statementfile required: The royalty statement: .csv, .xlsx or .xls, up to 10 MB.contractsfile, repeated: Contract PDFs: up to 10 files, 20 MB in total. Send the field once per file, named contracts (not contracts[]); several PDFs are merged into one set of terms.contract_termsJSON string: The contract, already structured (below). No AI runs, so it always costs the base price.expensesJSON string: Optional recoupable costs, recovered before net-basis shares are paid (below).Idempotency-Keyheader: Optional. The same Idempotency-Key, sent with the same API key and the same inputs in the same billing period, is charged once. Recommended on every call.
The statement file
One row per song earnings line. Remediio finds the two columns it needs by header name, case-insensitively: a title column (a header containing title, song, track, release title…) and an amount payable column (net payable, net earnings, net revenue, payable to artist…; failing that, payable, amount, earnings, payment or revenue, as long as the header doesn't say withheld, deduction, fee, commission or advance). Rows with the same title are summed, so one line per month or per platform needs no pre-aggregation.
Title,Net Payable Blue Sky,1000.00 Red Sun,500.00
Contract terms
contract_terms is the JSON form of a contract: parties (each with a name, a free-text role such as producer or artist, and optional aliases), works (each with a title, matched to statement titles loosely, so case, punctuation and suffixes such as “(Remix)” are tolerated) and royalty_shares. An optional default_basis (“gross” or “net”) applies to shares that set none. The Splits endpoint returns this exact shape from a PDF, so a contract parsed once can drive every later statement with no AI.
party_namestring required: Must name one of parties.royalty_typestring required: What income the share is paid from. The calculation covers streaming and master income, so use master or streaming (digital, DSP revenue and similar also count). Publishing, mechanical, sync and performance shares are ignored, because they are paid from different statements.percentagenumber required: 0–100, applied to each matched work the share covers.basis"gross" | "net": gross pays the percentage of the statement amount; net takes the song's recovered expenses off first. Falls back to default_basis, then gross.termsstring: The clause, verbatim if you have it. If it names SoundExchange, a PRO or the MLC as the payer, the share is treated as paid outside this statement and skipped.worksarray of string: Titles this share applies to, matched like works[].title. Empty (the default) means every work, and so does a list naming no work in works. Fill it only when the contract gives a different split per song, as a split sheet does: one share per party per title.
{
"parties": [{"name": "Jane Doe", "role": "producer"}],
"works": [{"title": "Blue Sky"}, {"title": "Red Sun"}],
"royalty_shares": [
{"party_name": "Jane Doe", "royalty_type": "master", "percentage": 50, "basis": "net"}
]
}parties, works and royalty_shares are JSON arrays inside contract_terms; expenses is its own array, sent as a separate field. One object per entry, as many as the deal has. Two producers on two songs is two parties, two works and two shares. The console builds them from rows and shows the exact body it sends.Expenses
expenses is a list of costs recovered before net-basis shares are paid; gross-basis shares ignore them. The API keeps no balance between calls, so a cost recovered over several statements is sent each time with what is left of it.
amountnumber required: ≥ 0, in the statement's currency.descriptionstring: For your records; echoed in recoupment.work_titlesarray of string: Songs this cost belongs to. Empty means project-wide.remainingnumber: ≥ 0. What is still to recover, when earlier statements recovered part of it: send back the carried figure from your last call. Omit to recover the full amount.
A cost is recovered once, never more than its remaining (or amount). A project-wide cost is spread across every song paid on net, in proportion to its earnings. A tagged cost is recovered only from its own songs, and only on a statement that pays one of them on net; otherwise it is left out. A statement recovers at most what those songs earned, and whatever it can't recover is reported as carried, not lost. A song's net amount never goes below zero.
[{"description": "Mastering", "amount": 300}]Response
A 200 stream. While a PDF is being read the server sends a heartbeat line (: ping) every 15 seconds; ignore lines starting with a colon. Exactly one data: event follows, carrying either a result or an error.
Result event
Five sections: the totals, one line per party per matched work, how the statement was read, what the expenses recovered, and what the call cost. The example is the statement and the contract_terms above with the 300.00 project-wide expense.
summary The totals, first.
paymentsinteger: How many payment lines follow.total_payablenumber: Sum of every line's payable amount, 2 dp.expense_review_requiredboolean: True when any line is on a net basis: the expense list changed the amounts and deserves a human check.
payments One line per party per matched song.
songstring: The work's title as given in the contract.payee.name, payee.rolestring: Who is paid, and the role the contract gives them (or unknown).share.typestring: The income the share is paid from, as written in the contract (master, streaming…).share.percentagenumber: The share applied, 0–100.share.basis"gross" | "net": The basis actually applied.amounts.grossnumber: What the statement paid for this song, all matching rows summed.amounts.expensesnumber: Costs recovered from this song's revenue; 0 on a gross share.amounts.netnumber: gross − expenses. Never negative: a statement only recovers what the song earned.amounts.payablenumber: net × percentage ÷ 100: the figure to pay. Amounts are rounded to 2 dp.
statement How the statement was read.
title_column_usedstring: The column the songs were read from. With several title-like columns (Release Title and Track Title), the one matching the most contract works wins; a tie goes to the track-level column.unmatched_worksarray of string: Contract works with no earnings under that column. They produce no payment lines, so check them before paying out.column_mapobject: Every statement column the run resolved: field name to the header as it appears in the file (title, amount, and any of vendor, country, sale_date, isrc… that were found).video_summaryobject | null: null unless the contract has an audio-visual share (share.type "master video"). Then it says how the revenue split into audio, video and unclear, and whether that share was applied.
recoupment What this statement recovered from expenses. null when no share is paid on net.
recoverednumber: Total recovered from all expenses on this statement.carriednumber: Total still to recover after it.expenses[]array: One entry per expense that applies to this statement, in the order sent: description, remaining (the balance it started from), recovered, carried. Send carried back as that expense's remaining with the next statement.
billing What this call cost.
creditsinteger: Credits charged for this call. 0 on a replay under the same Idempotency-Key.replayedtrue: Present only on a replay: this result was charged on its first run, not again.request_idstring: The id the charge is recorded under. Quote it to support.
Unmatched titles
Statement rows that match no work in the contract are skipped, not treated as errors: a statement can carry more songs than the contract covers. Contract works with no earnings are listed in statement.unmatched_works. If none of the contract's works appear in the statement, the run ends with a NO_SONG_MATCHES error event instead of a result, and nothing is charged.