by RUN

Advances,
inside your product.

Show your artists a RUN advance offer on your own pages, under your own name. We price every account ahead of time. You show one iframe.

2calls from your backend
1iframe on your page
0uploads for the artist

How it works

1. Connect onceRUN reads your accounts and their royalty statements from your backend, read-only. Nothing to build on your side.
2. We price aheadEvery account is priced before anyone asks, and marked eligible or not.
3. You askWhen an artist opens your dashboard, your backend asks if their account is eligible, and for how much.
4. You show the pageYour backend opens an offer page for that account and your page shows it in an iframe.
5. The artist choosesThey read how it works, see their offers and pick one, without leaving your product.

The page carries your product name, colours, language and currency. To the artist, the offer comes from you.

Quickstart

Three steps, once RUN has connected your data and given you a key.

Ask whether the account is eligible. Open the page for it. Embed the URL you get back.

account is your own id for the artist or label, the same one your backend already uses.

Base URL:

# 1. Is this account eligible?
curl {BASE}/accounts/label_48213/offer \
  -H "Authorization: Bearer {KEY}"

# 2. Open the offer page for it
curl -X POST {BASE}/embed/sessions \
  -H "Authorization: Bearer {KEY}" \
  -H "Content-Type: application/json" \
  -d '{"account": "label_48213", "locale": "es"}'

# 3. Put the "url" from step 2 in an iframe

Authentication

Send your secret key as a Bearer token on every /v1 request.

RUN issues your keys: sk_live_… for production and sk_test_… for testing. A key is shown once, when it is made.

Keep keys on your server. The browser never needs one: the iframe gets its own one-hour pass.

A missing, wrong or revoked key gets 401. Over your requests per minute you get 429 with a Retry-After header.

Sign in with your key and the samples fill it in. No key yet? Ask RUN for one.

Authorization: Bearer {KEY}

Is this account eligible?

GET/v1/accounts/{account}/offer

The newest open offer for one of your accounts. It answers at once, because the pricing was done ahead of time.

eligibleWhether to show this account the advance.
ineligible_reasonWhen not eligible: too_small or too_little_history.
optionsOne entry per offer: years, artist_share and the advance.
currencyThe currency the artist is shown.
expiresWhen the offer stops being valid.
selected_yearsThe option the artist chose, once they have.
signed_atWhen the artist signed the agreement. Empty until then.

No open offer for the account: 404. Use the biggest advance for a teaser such as "Get up to €205,000".

{
  "id": "ofr_4e6287d71f6146e2b1bc",
  "object": "offer",
  "account": "label_48213",
  "account_name": "Luna Roja Records",
  "eligible": true,
  "ineligible_reason": null,
  "currency": "EUR",
  "options": [
    { "years": 1, "artist_share": 0.8, "advance": 47000 },
    { "years": 3, "artist_share": 0.75, "advance": 135000 },
    { "years": 5, "artist_share": 0.7, "advance": 205000 }
  ],
  "created": "2026-11-12T02:00:00Z",
  "expires": "2026-11-26T02:00:00Z",
  "expired": false,
  "selected_years": null,
  "selected_at": null,
  "source": "precalc"
}

Open the offer page

POST/v1/embed/sessions

Call this from your backend when the artist clicks through. You get a URL for an iframe.

FieldDescription
accountYour id for the artist or label. Opens their newest open offer.
offerOr an offer id, ofr_…. Send one of the two.
localeThe page's language, one of those set up for you. Default: your first.
signerWho signs: name and email. With it, the artist signs the agreement in the page right after choosing. Without it, the page stops after choosing.

The URL works for one hour and for that offer only. Make a new one each time the artist opens the page.

An account that is not eligible cannot be opened: 400 not_eligible.

{
  "object": "embed_session",
  "id": "ems_0c3b1e4f5a6d7b8c9d0e",
  "offer": "ofr_4e6287d71f6146e2b1bc",
  "url": "https://…/embed?s=ems_…#es_…",
  "expires": "2026-11-12T15:04:05Z"
}

Embed it

Put the URL in an iframe on one of the sites registered for your account. The browser refuses to show it anywhere else.

The page tells your page what is happening with postMessage. Every message has source: "music-embed" and a type:

readyThe page has loaded.
resizeWith height in pixels. Set the iframe's height to it.
selectedWith years. The artist chose an offer.
signedWith years. The artist signed the agreement.
closeThe artist pressed "Not now" or "Close". Hide the iframe.

After the artist chooses, GET /v1/accounts/{account}/offer shows selected_years, and signed_at once they have signed. Trust that call, not the message: a message comes from the browser.

Try it with your key.

<iframe id="advance" src="URL"
  style="width:100%;border:0"></iframe>

<script>
  const frame = document.getElementById("advance");
  addEventListener("message", (e) => {
    if (e.source !== frame.contentWindow) return;
    if (e.data?.source !== "music-embed") return;
    if (e.data.type === "resize")
      frame.style.height = e.data.height + "px";
    if (e.data.type === "selected")
      console.log("chose", e.data.years, "years");
    if (e.data.type === "close") frame.hidden = true;
  });
</script>

What the artist sees

How it worksSix short points: priced from their royalties, repaid first, catalog locked for the term, unlocked at the end.
The offersOne card per offer, with the advance and what they keep. They pick one.
Their choiceA summary of the advance, the term and the split.

Signing the agreement in the same page comes next. Today the page stops after the choice and says the agreement is being prepared.

Set per partner

The offers (years and split), who is eligible, the product name, colours, languages, currency and how long an offer stays open. RUN sets these up with you.

Errors

Normal HTTP status codes and one JSON shape.

StatusTypeCodes
400invalid_request_errorinvalid_parameter, invalid_body, not_eligible, offer_expired, embed_not_configured
401authentication_errorinvalid_api_key
404invalid_request_errornot_found
429rate_limit_errorrate_limited
500api_errorinternal

The full contract is in the API reference and as OpenAPI.

{
  "error": {
    "type": "invalid_request_error",
    "code": "not_eligible",
    "message": "That account is not eligible for an advance right now."
  }
}

Versions

The version is in the path: /v1. Every reply carries Music-Version: v1.

We add things at any time: new routes, new optional request fields, new response fields, new codes, new page messages. So ignore fields, codes and message types you do not know.

We never do these inside a version: remove or rename a route or field, change a field's type or meaning, add a required request field, tighten what a field accepts, change how you sign in.

A change like that ships as /v2, next to /v1. We tell your technical contact, and /v1 keeps working for at least 12 months after that.

The page in the iframe is ours and improves without notice. Until your first live key, /v1 can still change; we tell you before it does.

HTTP/2 200
content-type: application/json
music-version: v1