The Harmony Library API
Overview
The harmony library holds 8,192 colour schemes across 23 methods — and holds none of them. The collection ships as a 5 KB manifest: a seed, a batch stride, and the canonical angles each scheme is defined by. Every record is computed from that. This guide is how to drive it from your own code, end to end.
One number is worth having before you start. Only 25.4% of the collection contains a pair of members you could set text with. Colour schemes are built from hue relationships, and hue relationships say nothing about lightness; a row of swatches cannot show you that, which is why every response here carries the contrast alongside the colours.
1. What this API is for
+
POST /v1/harmony/generate makes you a scheme from a base colour.
These four answer the questions on either side of that one.
- Show me schemes that actually work as UI —
/v1/harmony/library - Give me one whole, with the seed that reproduces it —
/v1/harmony/library/{id} - What is in the collection, counted? —
/v1/harmony/library/stats - What scheme is my palette? —
/v1/harmony/identify
The last one is the reason the other three are worth having. A collection of 8,192 schemes is not primarily a shop; it is the reference a matcher can be checked against. The library page runs the same matcher in your browser, so the page and the API cannot disagree.
2. Getting a key and making your first call
+Create a key in your dashboard under API keys, then send it as a bearer token.
curl -H "Authorization: Bearer $AURIC_API_KEY" \
"https://api.auricartisan.com/v1/harmony/library?limit=3"
3. Derived, not stored
+
This is the one structural fact worth understanding before you build on it.
harmonies.json is 5 KB and contains no harmonies. It holds a
seed (137), a batch size (1,024), a batch seed stride, the 23 method names,
how many colours each produces, which family each belongs to, and the
canonical hue angles that define it.
Record n is produced by seeding an xorshift generator per batch,
stepping it three times per record for a base hue, saturation and lightness,
and applying the scheme at n % 23. That is the whole database.
Which is why /v1/harmony/library/{id} returns a
derivation block with the seed and batch in it. A stored row is
something you have to trust; a derived one is something you can check.
4. Browsing and paging
+GET /v1/harmony/library?family=polyadic&sort=contrast&order=desc&limit=2
{
"collection": { "total": 8192, "methods": 23, "families": 5 },
"filter": { "family": "polyadic", "method": null, "hue": null, "colors": null },
"sort": "contrast",
"order": "desc",
"matched": 2138,
"share_of_collection": 26.1,
"next_offset": 2,
"harmonies": [
{
"id": 4395,
"harmony_id": "har_3e3",
"method": "triadic",
"family": "polyadic",
"color_count": 3,
"colors": ["#4C09E9", "#E94C09", "#09E94C"],
"adherence": 1,
"min_pair_contrast": 2.18,
"hue_spread": 240
}
]
}
Because the catalogue is finite and enumerated, matched is a real
total: filtering to the polyadic family tells you there are exactly 2,138 of
them, 26.1% of everything. Every filter walks all 8,192 derivations rather than
consulting an index — that is a few hundred milliseconds, and the
alternative is a table that can drift from the generator.
Filters
method— one of the 23, e.g.triadic.family—complementary,analogous,polyadic,monochromaticorcompound.hue— the base colour’s band:redthroughpink.colors— how many members, 2–7.min_contrast— the worst pair must clear this ratio. The one that matters; see the numbers in section 6.min_adherence— how closely it holds its canonical angles, 0–1.sort—id,contrast,spread,adherenceorcolors;orderisascordesc.full=true— adds per-colour HSL and OKLCH, the role each plays, and the canonical angles.
Page with next_offset, which is null on the last page.
5. One harmony, whole
+GET /v1/harmony/library/har_0
{
"harmony": {
"id": 0,
"harmony_id": "har_0",
"method": "complementary",
"family": "complementary",
"color_count": 2,
"base": { "hue": 3, "saturation": 0.5126, "lightness": 0.6313, "hex": "#D17671" },
"colors": [
{ "role": "base", "hex": "#D17671", "rgb": [209, 118, 113], "oklch": [...] },
{ "role": "complement", "hex": "#71CCD1", "rgb": [113, 204, 209], "oklch": [...] }
],
"canonical_angles": [180],
"adherence": 1,
"hue_spread": 180,
"min_pair_contrast": 1.72,
"derivation": {
"seed": 137,
"batch": 0,
"batch_size": 1024,
"batch_seed_stride": 2654435761,
"note": "This record is derived, not stored..."
}
}
}
Ids come in two spellings and name the same record: har_1p is
base-36 for 61, and /v1/harmony/library/61 returns the same thing.
adherence is the collection’s own measure of how closely a
record holds its canonical angles, taken from its first colour. Note that a few
families — the analogous ones especially — write those angles
relative to their middle member, so their adherence reads below 1 by
construction. If you want an anchor-independent answer, use
/identify, which is built for exactly that.
6. The collection’s own numbers
+GET /v1/harmony/library/stats
{
"collection": { "total": 8192 },
"mean_adherence": 0.943,
"mean_min_contrast": 1.274,
"mean_hue_spread": 148.96,
"with_an_aa_pair": { "count": 2082, "share": 25.42 },
"family": [
{ "name": "polyadic", "count": 2138, "share": 26.10 },
{ "name": "monochromatic", "count": 2136, "share": 26.07 },
{ "name": "analogous", "count": 1424, "share": 17.38 },
{ "name": "compound", "count": 1424, "share": 17.38 },
{ "name": "complementary", "count": 1070, "share": 13.06 }
],
"method": [ ... 23 entries ... ],
"color_count": [ ... ]
}
The number that should change how you use this
with_an_aa_pair is 25.42%. Three quarters of these
schemes contain no two members that can be set as text on each other. That is
not a defect in the generator — it is what colour harmony is.
Schemes are defined by hue relationships, and rotating the hue wheel does not
change lightness, so a textbook triadic is three colours of near-identical
luminance.
mean_min_contrast of 1.274 says the same thing
from the other side: the average scheme’s worst pair is barely
distinguishable at all.
So: pick by hue, then filter by min_contrast, and expect to add a
light and a dark of your own. A harmony gives you a palette’s
character; it does not give you a text colour.
7. Naming a scheme you already have
+POST /v1/harmony/identify
Content-Type: application/json
{ "colors": ["#FF0000", "#00FF00", "#0000FF"] }
{
"verdict": "a triadic — though by hue alone it is indistinguishable from triad shifted...",
"best_match": {
"method": "triadic",
"family": "polyadic",
"mean_angle_error": 0,
"fit": 1,
"base_color": "#FF0000",
"canonical_angles": [120, 240]
},
"ties": ["triad_shifted"],
"candidates": [ ...top five... ],
"hue_offsets": [0, 120, 240],
"min_pair_contrast": 2.15,
"max_pair_contrast": 6.26,
"carries_body_text": true
}
How the match is made
A harmony is a shape on the hue circle, and a shape has no
first element — rotate it and it is the same harmony. So each colour is
tried as the base in turn and the best fit wins. fit is one minus
the mean angular error over 60°, because 60° is a whole scheme’s
worth of wrong: at that much error a triadic reads as a square.
That rotation is not a nicety. Several schemes write their canonical angles
relative to their middle member — analogous_3 is
[-24, 24] — so a textbook analogous triad handed over in
reading order has offsets of +24° and +48° from its first colour and
matches nothing at all. Anchoring on each member fixes that, and makes the
answer independent of the order you send the colours in.
8. The five schemes hue cannot separate
+
Feed all 23 schemes back through /identify and 18 come back named
exactly. The other five come back as ties, and they are the same
five every time:
shades,tintsandneutral— five colours at one hue, exactly likemonochromatic_5. Only lightness and saturation tell them apart.warm_cool— two colours 180° apart. That is acomplementary.triad_shifted— atriadic’s 120° and 240°, with saturation moved.
A measurement that cannot distinguish two answers should return both, so
ties lists every scheme within a hundredth of the winner’s
fit and the verdict says so in words. If you need to separate them, the
lightness and saturation spread across the members is what does it —
full=true on a browse gives you the per-colour values.
9. Three complete tasks
+Find a scheme you can actually build a UI from
GET /v1/harmony/library?min_contrast=4.5&colors=2&sort=contrast&order=desc&limit=12
Two members, and the worst pair among them already clears AA. That is a small slice of 8,192, which is the point of asking for it explicitly rather than picking a pretty row of swatches and discovering the problem in review.
Name the scheme your brand already uses
const res = await fetch("https://api.auricartisan.com/v1/harmony/identify", {
method: "POST",
headers: { "content-type": "application/json", authorization: `Bearer ${KEY}` },
body: JSON.stringify({ colors: BRAND_PALETTE }),
});
const m = await res.json();
console.log(m.verdict);
if (m.best_match.fit < 0.65) {
console.log("Not a textbook scheme — hue offsets:", m.hue_offsets);
}
if (!m.carries_body_text) {
console.warn("No two members clear AA. You need a text colour from outside it.");
}
Cite a harmony in a design document
GET /v1/harmony/library/har_2la
The response carries the seed and batch, so the citation is checkable: anybody can recompute record 3,358 from seed 137 and get the same two colours. That is a different kind of reference from “here is a hex I liked”.
10. What each call costs
+GET /v1/harmony/library/{id}— 1 credit. One derivation.GET /v1/harmony/library— 3 credits. Derives and filters all 8,192.POST /v1/harmony/identify— 3 credits. Matches against 23 schemes, anchored on each colour.GET /v1/harmony/library/stats— 10 credits. Walks the whole collection and counts four dimensions.
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
+GET /v1/harmony/library?method=lovely
{
"error": {
"code": "INVALID_INPUT",
"message": "method: must be one of complementary, split_complementary, triadic, ..."
}
}
- Ids run 0–8191, in either spelling. Anything else is a validation error, not a 404.
colorsis 2–12 on identify. Fewer than two is not a harmony; more than twelve is not one either.- Only schemes of the same size are considered. Hand identify four colours and it will never answer “triadic”, however close three of them are.
- Do not read a high
adherenceas “this is a good scheme”. It measures conformity to canonical angles, nothing else — a perfectly adherent triadic can still be three colours you cannot tell apart in greyscale. - Mind the rate limit when looping. Fetch a page of records rather than an id at a time.
12. Endpoint reference
+| Endpoint | Method | Credits | Answers |
|---|---|---|---|
/v1/harmony/library | GET | 3 | Which schemes match this filter, and how much of the collection is that? |
/v1/harmony/library/{id} | GET | 1 | One harmony, with its roles, angles and the seed that reproduces it. |
/v1/harmony/library/stats | GET | 10 | What is in the collection, counted rather than asserted. |
/v1/harmony/identify | POST | 3 | Which of the 23 schemes do these colours form, and how closely? |
The machine-readable form is in the API reference, generated from the same catalogue the server routes from, so it cannot drift from the implementation.
To see all of this working without writing any code, open the harmony library — the browse grid and the “what harmony is this?” panel are the same matcher this API exposes.