Skip to main content
Auric Artisan · Documentation

The Gradient Library API

Date: August 28, 2026 For: Developers Category: Guide Author: Chirag Bansal
Back to Documentation API reference Open the library

Overview

The gradient library holds 8,192 generated ramps, and every one of them carries a measured verdict on whether it bands. This guide is how to drive the collection from your own code — getting a key, the four endpoints, and three complete tasks worth doing with them.

One number is worth having before you start. 77.4% of the collection is smooth, 18.6% carries a visible step risk and 4.0% compresses somewhere. Roughly one gradient in five steps visibly at some point along its length, and that is the one thing a thumbnail cannot show you — which is why every response here carries the verdict rather than leaving you to squint at a swatch.

Table of contents

  1. 1. What this API is for
  2. 2. Getting a key and making your first call
  3. 3. What the collection is
  4. 4. Browsing and paging
  5. 5. One gradient, whole
  6. 6. The collection’s own numbers
  7. 7. Measuring a gradient you already have
  8. 8. Two verdicts, two curves
  9. 9. Three complete tasks
  10. 10. What each call costs
  11. 11. Errors, limits and pitfalls
  12. 12. Endpoint reference

1. What this API is for

+

POST /v1/gradient/generate makes you a gradient. That is a different job from the one these four endpoints do, which is to answer questions about gradients that already exist — ours, or yours.

  • Show me ramps that do not band — /v1/gradient/library
  • Give me one of them whole, with its metrics — /v1/gradient/library/{id}
  • What is in the collection, counted? — /v1/gradient/library/stats
  • Will my gradient band, and where? — /v1/gradient/analyse

The last one is the reason the other three are worth having. A collection of 8,192 measured ramps is not primarily a shop; it is the calibration set that made the thresholds in /v1/gradient/analyse mean something. The library page runs the identical measurement in your browser, so the page and the API cannot disagree about a ramp.

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. Every endpoint on this page is versioned under /v1.

curl -H "Authorization: Bearer $AURIC_API_KEY" \
  "https://api.auricartisan.com/v1/gradient/library?limit=3"

Three of the four are GETs with no body, so the quickest way to see a real response is to paste one into a browser tab with your key on it. The fourth, /v1/gradient/analyse, takes JSON.

3. What the collection is

+

8,192 gradients, generated rather than collected, across twelve schemes (Aurora, Nebula, Spectral, Terrain, Thermal, Golden, Analogous, Complementary, Duotone, Monochrome, Prismatic and Chaotic) at roughly 683 each. Each was built in one of five interpolation spaces — oklch-short, oklch-long, oklab, lch-short or linear-rgb — at about 1,638 apiece, under one of five easing curves.

Because it is finite and enumerated, matched in a browse response is a real total, not a scan rate: filtering for the smooth ones tells you there are exactly 6,342 of them, which is 77.42% of everything.

Ids are row numbers, 0 through 8191, and they are stable. The name is derived from the row — Aurora Simple #00001 is id 0 — so a name and an id are two spellings of the same reference.

4. Browsing and paging

+
GET /v1/gradient/library?banding=smooth&sort=score&order=desc&limit=2
{
  "collection": { "total": 8192 },
  "filter": { "banding": "smooth", "scheme": null, "complexity": null, "type": null },
  "sort": "score",
  "order": "desc",
  "matched": 6342,
  "share_of_collection": 77.42,
  "offset": 0,
  "limit": 2,
  "count": 2,
  "next_offset": 2,
  "gradients": [
    {
      "id": 2050,
      "name": "Prismatic Detailed #02051",
      "type": "linear",
      "angle": 350,
      "stops": [
        { "pos": 0, "hex": "#A3E817" },
        { "pos": 0.2035, "hex": "#1444C7" }
      ],
      "css": "linear-gradient(350deg, #a3e817 0%, #0049c8 20%, ...)",
      "scheme": "Prismatic",
      "complexity": "detailed",
      "banding": "smooth",
      "score": 0.782
    }
  ]
}

stops and css are not the same list

This trips people up, so it is worth being explicit. stops are the control points, at their real positions, in the gradient’s own interpolation space. css is a ready-to-paste linear-gradient() that approximates that curve by resampling it at even positions in sRGB, because a browser cannot interpolate in OKLCH from the control points alone.

Paste css when you want the ramp. Read stops when you want to know what it was built from. They describe the same gradient and they will not match token for token.

Paging

Pass next_offset back as offset. It is null on the last page. Nothing here is a cursor over a scan — the catalogue is finite, so an offset is exactly what it looks like.

GET /v1/gradient/library?banding=smooth&sort=score&order=desc&limit=2&offset=2

Filters

  • banding — smooth, subtle-compression or visible-step-risk. The one to reach for.
  • scheme — one of the twelve, e.g. Aurora.
  • complexity — simple, detailed or extreme.
  • type — linear, radial or conic.
  • min_score — a quality floor, 0–1.
  • min_stops, max_stops — how many control points.
  • sort — id (default), score or stops; order is asc or desc.
  • full=true — adds the interpolation space, easing, per-stop OKLCH and the generator metrics to every record in the page.

Leave full off for a grid. A tile needs the stops, the verdict and the score; the interpolation space and the ΔE metrics are what you read when you open one.

5. One gradient, whole

+
GET /v1/gradient/library/0
{
  "gradient": {
    "id": 0,
    "name": "Aurora Simple #00001",
    "type": "linear",
    "angle": 0,
    "stops": [
      { "pos": 0, "hex": "#723C2D" },
      { "pos": 1, "hex": "#7B3600" }
    ],
    "css": "linear-gradient(0deg, #723c2d 0%, #7b3600 100%)",
    "scheme": "Aurora",
    "complexity": "simple",
    "banding": "smooth",
    "score": 0.4045,
    "stops_detail": [
      { "pos": 0, "hex": "#723C2D", "oklch": [0.4204, 0.08, 36.4] },
      { "pos": 1, "hex": "#7B3600", "oklch": [0.4194, 0.1115, 49.6] }
    ],
    "method": "aurora",
    "interpolation_space": "oklch-short",
    "easing": "linear",
    "sample_count": 96,
    "complexity_score": 0.1005,
    "delta_e_mean": 0.1732,
    "uniformity": 0.0391,
    "seed": 20260514
  }
}

seed is the useful one for reproducibility: it pins the gradient to the generator run that produced it, so a record can be cited and regenerated rather than merely copied.

6. The collection’s own numbers

+
GET /v1/gradient/library/stats
{
  "collection": { "total": 8192 },
  "mean_score": 0.5109,
  "mean_stops": 9.99,
  "banding": [
    { "name": "smooth",             "count": 6342, "share": 77.42 },
    { "name": "visible-step-risk",  "count": 1520, "share": 18.55 },
    { "name": "subtle-compression", "count":  330, "share":  4.03 }
  ],
  "complexity": [ ... ],
  "scheme": [ ... ],
  "interpolation_space": [ ... ],
  "easing": [ ... ]
}

The banding split is the headline, and it is the fact that makes the collection worth having. A generated set is mostly smooth — but nothing like entirely so, and a set that came back 99% smooth would be evidence that the measurement was not measuring anything.

7. Measuring a gradient you already have

+
POST /v1/gradient/analyse
Content-Type: application/json

{ "stops": ["#000000", "#ffffff"] }
{
  "stops": [ { "pos": 0, "hex": "#000000" }, { "pos": 1, "hex": "#ffffff" } ],
  "steps": 96,
  "metric": "Euclidean distance in OKLab between consecutive samples",
  "sampled_as": "linear between stops in sRGB \u2014 the ramp a browser paints",
  "delta_e_mean": 0.010526,
  "delta_e_max": 0.093398,
  "step_ratio": 8.873,
  "uniformity": 0.008802,
  "banding": "visible-step-risk",
  "worst_transition_at": 0,
  "note": "The largest step is at the dark end. sRGB compresses its darkest values, so even a plain black-to-white ramp steps there \u2014 interpolate in OKLCH, or start from a lifted black rather than #000."
}

How the verdict is reached

The ramp is sampled at 96 points (steps, 8–1024), each sample is converted to OKLab, and the distance between consecutive samples is measured. step_ratio is the largest of those steps over the average one. A ratio rather than an absolute is deliberate: it does not move when you ask for more samples, so a verdict at 32 steps and a verdict at 512 agree.

The thresholds are not invented. Measured across 3,000 gradients this collection had already labelled, that ratio runs a median of 1.55 for the ones the generator calls smooth and 5.09 for the ones it calls a step risk. 3.2 and 5.5 are where those two populations part.

The part you can act on

worst_transition_at is a fraction along the ramp, and it matters more than the verdict. A gradient can be smooth on average and lurch once; knowing it lurches at 95% tells you to put a stop near there. The note says the same thing in a sentence, and it distinguishes the two cases — a step near zero is sRGB’s dark compression, not a missing stop, and no amount of extra stops will fix it.

Both stop formats

Pass bare colours and they are spaced evenly; pass {pos, hex} objects to place them. Two to 64 stops.

{ "stops": [
    { "pos": 0,    "hex": "#001a33" },
    { "pos": 0.92, "hex": "#003d66" },
    { "pos": 1,    "hex": "#ffcc00" }
] }

→ step_ratio 10.807, banding "visible-step-risk", worst_transition_at 0.947

8. Two verdicts, two curves

+

Feed a stored record’s stops into /v1/gradient/analyse and you will sometimes get a different verdict from the one the record carries. That is not a bug in either, and it is worth understanding before you build anything on top of it.

  • A record’s banding describes the ramp the generator built, along a curve in its own interpolation space — often OKLCH or LCH.
  • /v1/gradient/analyse describes the ramp a browser paints: linear, in sRGB, between the stops it was given.

How far apart those are depends entirely on how much the stop list constrains the curve. Sampling the collection by stop count, and asking only whether each ramp is smooth:

  • 2 stops (1,258 records) — the two agree 100% of the time. There is no room to disagree.
  • 3 to 9 stops (5,569 records) — 77.5%. The interpolation space is doing most of the shaping, and the two are describing different curves.
  • 10 or more stops (1,365 records) — 94.1%. The stop list pins the curve down, so both measurements see nearly the same ramp.

Those are counted over every record in each group, not a sample — 83.7% across the whole collection. It matters here: 12 generator methods cycle through the records in order, so a fixed stride lands on a particular mix of them and answers differently depending on the stride you picked.

The practical rule: to know how a stored gradient will look on your page, analyse its css stops, because that is what the browser will actually paint. The record’s own banding is the generator’s verdict on the ideal ramp it was aiming at.

9. Three complete tasks

+

Check the gradients already in your stylesheet

The most useful thing here is not picking a new gradient; it is finding out which of the ones you already ship will band on a large hero.

const ramps = {
  hero:    ["#0f2027", "#203a43", "#2c5364"],
  cta:     ["#000000", "#434343"],
  sidebar: ["#001a33", "#003d66", "#ffcc00"],
};

for (const [name, stops] of Object.entries(ramps)) {
  const res = await fetch("https://api.auricartisan.com/v1/gradient/analyse", {
    method: "POST",
    headers: { "content-type": "application/json", authorization: `Bearer ${KEY}` },
    body: JSON.stringify({ stops }),
  });
  const m = await res.json();
  if (m.banding !== "smooth") {
    console.warn(`${name}: ${m.banding} \u2014 worst at ${Math.round(m.worst_transition_at * 100)}%`);
    console.warn(`  ${m.note}`);
  }
}

Pick a background that will survive being 1200px wide

GET /v1/gradient/library?banding=smooth&min_stops=10&sort=score&order=desc&limit=12

Smooth and many-stopped is the combination that holds up at size, and it is the population where the stored verdict and the painted ramp agree 94% of the time — so the label you filtered on is the label you will get.

Find where to add a stop

const m = await analyse(stops);
if (m.banding !== "smooth" && m.worst_transition_at > 0.06) {
  // Not the sRGB dark end \u2014 a genuine gap. Interpolate a stop at the
  // midpoint of the worst transition and measure again.
  const at = m.worst_transition_at;
  const fixed = insertStopAt(stops, at);
  console.log(await analyse(fixed));   // step_ratio should fall
}

The > 0.06 guard matters. A worst transition at the very start is sRGB’s dark compression, and adding a stop there does nothing; the fix is to lift the black or interpolate in OKLCH.

10. What each call costs

+
  • GET /v1/gradient/library/{id} — 1 credit. A lookup.
  • GET /v1/gradient/library — 3 credits. Filters and sorts the whole catalogue.
  • POST /v1/gradient/analyse — 3 credits. Samples a ramp and converts every sample to OKLab.
  • GET /v1/gradient/library/stats — 10 credits. Walks all 8,192 records and counts five dimensions.

API credits are a separate balance from tool tokens; neither pays for the other. Statistics do not change between calls — cache the stats response rather than fetching it per page render.

11. Errors, limits and pitfalls

+

A rejected filter names itself and lists what it would have accepted.

GET /v1/gradient/library?banding=lovely

{
  "error": {
    "code": "INVALID_INPUT",
    "message": "banding: must be one of smooth, subtle-compression, visible-step-risk"
  },
  "request_id": "6f4204af-38ba-4114-8ba3-ec2aa47858e1"
}
  • Ids are 0-based, names are 1-based. Aurora Simple #00001 is id 0. An id outside 0–8191 is a validation error, not a 404.
  • limit is 1–100, default 24.
  • stops is 2–64 on analyse, and steps is 8–1024.
  • Do not compare step_ratio across different step counts as if it were a ΔE. It is a shape measure; delta_e_max is the magnitude one.
  • Do not read css and stops as the same list. See section 4.

12. Endpoint reference

+
EndpointMethodCreditsAnswers
/v1/gradient/libraryGET3Which ramps match this filter, and how much of the collection is that?
/v1/gradient/library/{id}GET1One gradient, with its per-stop OKLCH and generator metrics.
/v1/gradient/library/statsGET10What is in the collection, counted rather than asserted.
/v1/gradient/analysePOST3Will this ramp band, how badly, and where?

The machine-readable form of all four is in the API reference, which is 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 gradient library — the browse grid, the verdict on every card and the “will your gradient band?” panel are the same measurement this API exposes.