Spicy LyricsDocs

Keys

If your code runs on a server you control, use the secret key. If it ships to users, use a client key with an origin allowlist.

Which one do you need?

Does your code run on a server you control?

  • Yes → secret key. Read it from an environment variable.
  • No → client access, and here is what it cannot do.

That is the decision. If you are building a backend, a script, a bot, or anything where you control the machine, stop reading and use the secret key.

If your code runs on a server you control, use the secret key from an environment variable. If it runs in a browser, browser extension, or any client you ship to users, use a publishable key and configure an origin allowlist. Never put a secret key in client-side code.

The honest comparison

If your code runs on a server, use the secret key. It is strictly better: higher limits, and rotating it is instant and affects nobody but you.

A publishable key is baked into shipped code, so rotating it breaks every installed copy until users update, which may take weeks. Its origin allowlist deters key theft rather than preventing it.

None of that is discouragement if you genuinely have no backend. A widget on a static site, or a browser extension, cannot keep a secret, and pretending otherwise by shipping a secret key is much worse than using the key type designed for the situation.

Secret keys

export SPICY_LYRICS_SECRET_KEY="sl_sk_..."
import os, requests

requests.get(
    "https://api.spicylyrics.org/v1/lyrics/TRACK_ID",
    headers={"Authorization": f"Bearer {os.environ['SPICY_LYRICS_SECRET_KEY']}"},
)
  • The higher per-application limit, with no per-viewer limit on top of it.
  • Rotating is instant, and you can optionally keep the old key alive for 15 or 60 minutes if you are coordinating a deploy. If you are rotating because the key leaked, do not use one. A grace window keeps the leaked key working for exactly as long as it helps you.
  • Shown once, at creation. Only a hash is stored, so it cannot be shown again.

Client access

Enabled per application from the dashboard, as a deliberate step. The flow will not issue a key until you supply at least one allowed origin, because a publishable key with an empty allowlist refuses every request.

// Client-side code. This key is public by design: it ships in your code and
// anyone can read it.
const response = await fetch("https://api.spicylyrics.org/v1/lyrics/TRACK_ID", {
  headers: { Authorization: "Bearer sl_pk_..." },
})

What it gives up:

  • A per-viewer IP limit on top of the per-application limit.
  • An origin allowlist to keep current, and a key with an empty one refuses every request.
  • Rotation is immediate and has no grace window, because a grace window measured in minutes does not help something that takes weeks to propagate through installed copies.

Origin allowlists

An origin allowlist is abuse deterrence, not authentication. Origin and Referer are trivially forged outside a browser. What an allowlist actually prevents is someone lifting your key off your site and using it on theirs. It does not prevent a determined person using the key from curl, and nothing in this design leans on it as a security boundary.

Supported entries:

KindExampleNotes
Exact originhttps://example.comScheme, host and port. No path.
Wildcard subdomainhttps://*.example.comAllows every subdomain, including any a third party controls.
Chrome extensionchrome-extension://<id>The id is stable across installs, so this identifies one extension.
Firefox extensionsmoz-extension://*Firefox gives every installation its own UUID, so a single Firefox extension cannot be allowlisted by origin. This allows any Firefox extension, and is materially weaker than the Chrome case.
Null originnullSandboxed iframes and some file:// contexts.
No origin headerNot applicableOff by default, behind its own confirmation. It turns the key back into a plain bearer token that any non-browser client can use by omitting a header. For Spicetify and native clients only.

Apps you ship to other people

If your app runs on other people's devices and each of them needs their own key, do not ask them to sign up and create an application. Submit your app as an app template from the dashboard instead.

Once it is approved, you get a link to its page in the catalog. Each person who opens it clicks Add and gets their own application with the keys your app needs, already configured: the key type, the origin allowlist and the rate limit come from the template. It does not use one of their application slots, and each copy has its own rate limit.

A template is the wrong tool for your own backend. One application and its secret key serve every one of your users.

Both at once

An application can hold a secret key and client access at the same time. A project with a web frontend and a backend job legitimately needs both. They are separate credentials with separate limits, and rotating one does not affect the other.

On this page