The Shade Library API
Overview
A shade scale is not a palette. A palette is a set of colours that look good together; a scale is one colour taken through lightness so that some steps are surfaces and other steps are text on them. That is a different job, and it has different failure modes — which is why the measurements here are not the ones the palette or harmony collections use.
One number before you start. All 8,192 scales in this collection run one way, and all of them can carry body text — but only 8.2% have genuinely even steps. Monotonicity and contrast are table stakes that the generator always gets right. Evenness is what actually separates a usable token ramp from one that feels arbitrary in use, and it is the filter to reach for.
1. What this API is for
+
POST /v1/shade/scale generates a ramp from a base colour. These
four are about ramps that already exist — ours, or yours.
- Show me scales that are actually even —
/v1/shade/library - Give me one whole, with every measurement —
/v1/shade/library/{id} - What is in the collection, counted? —
/v1/shade/library/stats - Does my token ramp work? —
/v1/shade/evaluate
The last one is the reason the other three are worth having. 8,192 measured scales are the reference; the interesting question is whether the scale in your design system passes the same tests.
2. Getting a key and making your first call
+curl -H "x-api-key: $AURIC_API_KEY" \
"https://api.auricartisan.com/v1/shade/library?evenness=even&limit=3"
3. The three things a scale has to get right
+It has to run one way
monotonic. If lightness rises and falls, two different steps read
as the same tone and one of them is doing no work. worst_reversal
says how far the biggest backwards move went, in OKLab lightness.
Its steps have to be evenly spaced
step_ratio is the largest step over the average one, measured as
OKLab distance between consecutive members. A ratio, not an absolute, so it
does not move with the number of steps — a 5-step and a 21-step scale are
directly comparable. Under 1.6 is even, under 2.6 is
slightly-uneven, above that is lumpy.
This is the same shape of measure /v1/gradient/analyse uses for
banding, and for the same reason: a ramp that lurches somewhere reads as a
step, whether it is painted as a gradient or shipped as tokens.
You have to be able to set text on it
usable_span is the contrast between the lightest and darkest
steps; text_pairs counts how many step pairs clear AA and AAA.
A scale can be perfectly monotonic and perfectly even and still be four shades
of off-white — a surface ramp, not a text scale.
4. Browsing and paging
+GET /v1/shade/library?evenness=even&sort=span&order=desc&limit=2
{
"collection": { "total": 8192, "methods": 10 },
"matched": 670,
"share_of_collection": 8.18,
"next_offset": 2,
"scales": [
{
"id": 1064,
"shade_id": "shade_0000tk",
"name": "Ink Paper #006B4C #01065",
"method": "ink-paper",
"base": "#006B4C",
"steps": [
{ "token": "100", "hex": "#FCFDFC" },
{ "token": "300", "hex": "#A3AAA6" },
{ "token": "500", "hex": "#006B4C" },
{ "token": "700", "hex": "#232826" },
{ "token": "900", "hex": "#010101" }
],
"step_count": 5,
"evenness": "even",
"monotonic": true,
"usable_span": 20.47,
"carries_body_text": true
}
]
}
Every step carries the token it answers to — 100, 300, 500 — not just a hex, so a scale can be dropped into a token file without re-deciding what to call each rung.
Filters
evenness—even,slightly-unevenorlumpy. The one that separates the collection.method— one of ten, e.g.tailwind-like,oklch-ramp,ink-paper.steps— exactly this many rungs, 2–64.hue— the base colour’s band,redthroughpink.min_span— contrast between the extremes.min_score— the generator’s own quality floor.monotonic,carries_body_text— both true for every record, so useful mainly as assertions.sort—id,score,steps,spanorevenness. Ascending evenness means evenest first, which is what anyone asking for it means.
5. One scale, whole
+GET /v1/shade/library/shade_000000
Adds the full measurement block: mean_step,
max_step, lightness_range, worst_reversal
and the AA/AAA pair counts.
Shade ids are base 36. shade_00000a is row 10 and
shade_000012 is row 38, not 12. Both spellings work —
/v1/shade/library/12 and /v1/shade/library/shade_00000c
name the same scale — but reading the digits as decimal will quietly hand
you the wrong record for every row past nine.
6. The collection’s own numbers
+GET /v1/shade/library/stats
{
"collection": { "total": 8192 },
"mean_score": 0.631,
"mean_step_ratio": 2.19,
"mean_usable_span": 18.87,
"monotonic": { "count": 8192, "share": 100 },
"carries_body_text": { "count": 8192, "share": 100 },
"evenness": [
{ "name": "slightly-uneven", "count": 6380, "share": 77.88 },
{ "name": "lumpy", "count": 1142, "share": 13.94 },
{ "name": "even", "count": 670, "share": 8.18 }
],
"method": [ ...10 entries... ],
"step_count": [ ...8 entries... ]
}
Two of these are 100%, and that is worth stating plainly rather than burying:
monotonicity and text-carrying are not useful filters here,
because the generator never gets them wrong. If monotonic.share
ever drops below 100, something in the generator has regressed.
Evenness is the discriminating measure. The mean step ratio is 2.19 — the average scale’s biggest jump is more than twice its average one — and fewer than one in twelve comes in under 1.6.
7. Measuring a scale you already ship
+POST /v1/shade/evaluate
Content-Type: application/json
{ "steps": ["#FFFFFF", "#E0E0E0", "#A0A0A0", "#606060", "#202020"] }
{
"verdict": "a clean scale",
"monotonic": true,
"worst_reversal": 0,
"step_ratio": 1.298,
"evenness": "even",
"usable_span": 16.29,
"text_pairs": { "aa": 3, "aaa": 2, "total": 10 },
"carries_body_text": true,
"notes": [
"5 of 10 step pairs clear AA, so you can build surface-and-text
combinations inside the scale itself."
]
}
Order matters, because monotonicity is about order — send the steps as your scale declares them. Either direction is fine: light-to-dark and dark-to-light both count as monotonic, and the verdict is identical for a scale and its reverse.
8. The three ways a scale fails
+Each has a different fix, so the response names which one happened rather than returning a single score.
It turns round
["#FFFFFF", "#202020", "#E0E0E0", "#606060"]
→ "not a usable ramp" · monotonic: false · worst_reversal: 0.6632
Reorder it. Two of those steps currently read as the same tone.
It is lumpy
["#FFFFFF", "#FEFEFE", "#FDFDFD", "#101010"]
→ "a lumpy scale" · monotonic: true · step_ratio: 2.978
It runs the right way — that is not the problem. Three of those steps are indistinguishable and the fourth is a cliff. Redistribute the rungs; an OKLCH ramp is the usual fix.
It is a surface ramp, not a text scale
["#FFFFFF", "#F6F6F6", "#EDEDED", "#E4E4E4"]
→ "a surface ramp, not a text scale" · evenness: even · usable_span: 1.27
Perfectly monotonic, perfectly even, and useless for setting type on itself. A verdict that only looked at evenness would call this the best scale in the world. Extend the range, or accept that the text colour comes from outside it.
9. Three complete tasks
+Audit the ramps in your design system
import tokens from "./tokens.json" with { type: "json" };
for (const [name, scale] of Object.entries(tokens.color)) {
const r = await (await fetch("https://api.auricartisan.com/v1/shade/evaluate", {
method: "POST",
headers: { "content-type": "application/json", "x-api-key": KEY },
body: JSON.stringify({ steps: Object.values(scale) }),
})).json();
if (r.verdict !== "a clean scale") {
console.warn(`${name}: ${r.verdict}`);
r.notes.forEach((n) => console.warn(" " + n));
}
}
Find a replacement for one that failed
GET /v1/shade/library?evenness=even&steps=11&hue=blue&sort=span&order=desc&limit=6
Eleven rungs, cleanly spaced, blue, widest range first. Note that
steps=11 is worth pinning: 36.5% of the collection is eleven-step,
which is what most token systems expect.
Check a generated scale before you ship it
const made = await post("/v1/shade/scale", { base: "#2E6FF2", steps: 11 });
const checked = await post("/v1/shade/evaluate", { steps: made.steps.map((s) => s.hex) });
if (checked.evenness === "lumpy") { /* try another method */ }
Generating and checking are different endpoints on purpose: the generator optimises for its method, and this tells you what the result actually measures.
10. What each call costs
+GET /v1/shade/library/{id}— 1 credit.GET /v1/shade/library— 3 credits.POST /v1/shade/evaluate— 3 credits.GET /v1/shade/library/stats— 10 credits. Walks all 8,192 and every pairwise contrast in each.
API credits are a separate balance from tool tokens; neither pays for the other. Statistics do not change between calls — cache them.
11. Errors, limits and pitfalls
+- Shade ids are base 36. See section 5. This is the one that will catch you.
- Send steps in scale order. Monotonicity is a statement about order; a shuffled scale is correctly reported as broken.
- 3 to 64 steps on evaluate. Two colours are not a ramp — there is only one gap, so evenness is meaningless.
- Do not filter on
monotonicorcarries_body_textexpecting them to narrow anything. Both are 100% in this collection. step_ratiois a shape measure, not a magnitude. A scale with tiny even steps and one with large even steps both score near 1.
12. Endpoint reference
+| Endpoint | Method | Credits | Answers |
|---|---|---|---|
/v1/shade/library | GET | 3 | Which scales match this filter, and how much of the collection is that? |
/v1/shade/library/{id} | GET | 1 | One scale, with every measurement. |
/v1/shade/library/stats | GET | 10 | What the collection contains, counted rather than asserted. |
/v1/shade/evaluate | POST | 3 | Does this ramp run one way, step evenly, and carry text? |
The machine-readable form is in the API reference, generated from the same catalogue the server routes from.
To see it working without writing any code, open the shade library — the evenness filter and the measurements on every card are the same ones this API exposes.