Remediio

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.

Base URL
https://api.remediio.com
Auth
Authorization: Bearer mk_live_…
POST /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.

  • statement file required: The royalty statement: .csv, .xlsx or .xls, up to 10 MB.
  • contracts file, 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_terms JSON string: The contract, already structured (below). No AI runs, so it always costs the base price.
  • expenses JSON string: Optional recoupable costs, recovered before net-basis shares are paid (below).
  • Idempotency-Key header: 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.

statement.csv: the smallest valid statement
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_name string required: Must name one of parties.
  • royalty_type string 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.
  • percentage number 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.
  • terms string: 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.
  • works array 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.
contract_terms
{
  "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"}
  ]
}
Lists
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.

  • amount number required: ≥ 0, in the statement's currency.
  • description string: For your records; echoed in recoupment.
  • work_titles array of string: Songs this cost belongs to. Empty means project-wide.
  • remaining number: ≥ 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.

expenses
[{"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.

  • payments integer: How many payment lines follow.
  • total_payable number: Sum of every line's payable amount, 2 dp.
  • expense_review_required boolean: 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.

  • song string: The work's title as given in the contract.
  • payee.name, payee.role string: Who is paid, and the role the contract gives them (or unknown).
  • share.type string: The income the share is paid from, as written in the contract (master, streaming…).
  • share.percentage number: The share applied, 0–100.
  • share.basis "gross" | "net": The basis actually applied.
  • amounts.gross number: What the statement paid for this song, all matching rows summed.
  • amounts.expenses number: Costs recovered from this song's revenue; 0 on a gross share.
  • amounts.net number: gross − expenses. Never negative: a statement only recovers what the song earned.
  • amounts.payable number: net × percentage ÷ 100: the figure to pay. Amounts are rounded to 2 dp.

statement How the statement was read.

  • title_column_used string: 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_works array of string: Contract works with no earnings under that column. They produce no payment lines, so check them before paying out.
  • column_map object: 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_summary object | 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.

  • recovered number: Total recovered from all expenses on this statement.
  • carried number: 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.

  • credits integer: Credits charged for this call. 0 on a replay under the same Idempotency-Key.
  • replayed true: Present only on a replay: this result was charged on its first run, not again.
  • request_id string: 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.