API
Endpoints

API Endpoints

Every endpoint below is a POST to https://api.trycolors.com/v1/... with a JSON body and these headers:

Content-Type: application/json
X-API-KEY: your_api_key_here

All examples on this page were run against the live API. Responses are real, trimmed where marked.

Common behaviour

  • A missing key returns 401 with the plain-text body Authentication required: X-API-KEY or X-Session-ID header. An unknown key returns 401 with Invalid API Key. These two are plain text, not JSON.
  • A path that is not listed on this page returns 404 with {"error":"Endpoint not found","docs":"https://api.trycolors.com/docs"}.
  • Validation failures return 400 with {"error":"..."}.
  • Over the rate limit returns 429. See Rate Limits.
  • Responses do not carry an engine version or a request id. If you store a recipe, store the engine, mixerMode and paint ids you sent with it.

Unmix Color

Finds the mix of your paints that comes closest to a target color.

  • URL: /v1/unmix-color
  • Method: POST

Request body

FieldTypeRequiredNotes
colorsarrayyesAt least 2 entries. Each entry is either a hex string or an object (see below).
targetHexstringyesThe color to reach, #RRGGBB.
maxColorsCountintegernoMost paints the recipe may use. Positive whole number. Defaults to the number of paints you sent.
maxDropsCountintegernoMost parts any single paint may take in the recipe. The whole recipe never exceeds 50 parts. Positive whole number, default 30.
mixerModestringno"basic" or "pro". Default "basic".
enginestringno"2023" or "2025". Default "2023". Only read when mixerMode is "pro".
tintingStrengthModestringno"uniform" or "adjusted". Default "uniform". Ignored when engine is "2025".

A colors entry is either a plain hex string:

["#00FFFF", "#FF00FF", "#FFFF00"]

or an object:

FieldTypeRequiredNotes
hexstringyes#RRGGBB. Case insensitive on input, echoed back as you sent it.
namestringnoYour label for the paint. Echoed back in structure, practicalSteps and omittedColors.
paint_idintegernoA Trycolors catalog paint id. When present, the engine uses that paint's fitted mixing curve instead of guessing from the hex.

paint_id is what makes a recipe reproducible with real tubes. Without it the engine only knows the color you typed; with it the engine knows the pigment behind that color.

Modes and engines

mixerModeengineWhat runs
basicignoredSimple weighted average in RGB. Free tier behaviour.
pro2023Legacy mixing algorithm. The default when you send mixerMode: "pro" and no engine.
pro2025Kubelka-Munk spectral mixing over fitted paint curves.

mixerMode: "pro" with engine: "2025" is the same calculation as the Pro mode of the mixer on trycolors.com. Send both, or you get the legacy 2023 engine.

Example request

{
  "colors": [
    { "hex": "#F7F5F1", "name": "Titanium White", "paint_id": 3554 },
    { "hex": "#19123F", "name": "Ultramarine Blue", "paint_id": 3656 },
    { "hex": "#FFC500", "name": "Cadmium Yellow Medium", "paint_id": 3573 },
    { "hex": "#673020", "name": "Burnt Sienna", "paint_id": 3610 },
    { "hex": "#5D162D", "name": "Quinacridone Magenta", "paint_id": 3650 }
  ],
  "targetHex": "#6E8B74",
  "maxColorsCount": 4,
  "maxDropsCount": 20,
  "mixerMode": "pro",
  "engine": "2025"
}

Those five paint ids are Golden Heavy Body tubes in the Trycolors catalog.

Example response

{
  "structure": [
    { "hex": "#F7F5F1", "name": "Titanium White", "count": 0.47368421052631576, "wholeParts": 9, "tintingStrength": 26, "paint_id": 3554 },
    { "hex": "#19123F", "name": "Ultramarine Blue", "count": 0.3157894736842105, "wholeParts": 6, "tintingStrength": 77, "paint_id": 3656 },
    { "hex": "#FFC500", "name": "Cadmium Yellow Medium", "count": 0.10526315789473684, "wholeParts": 2, "tintingStrength": 21, "paint_id": 3573 },
    { "hex": "#673020", "name": "Burnt Sienna", "count": 0.10526315789473684, "wholeParts": 2, "tintingStrength": 61, "paint_id": 3610 },
    { "hex": "#5D162D", "name": "Quinacridone Magenta", "count": 0, "wholeParts": 0, "tintingStrength": 67, "paint_id": 3650 }
  ],
  "mixedColor": "#718C76",
  "matchResult": 99.2,
  "ratioMixedColor": "#718C76",
  "ratioMatchResult": 99.2,
  "practicalSteps": [
    {
      "step": 1,
      "color": { "hex": "#FFC500", "name": "Cadmium Yellow Medium", "wholeParts": 2, "tintingStrength": 21 },
      "cumulative": { "#FFC500": 2 },
      "mixedColor": "#FFC500",
      "matchResult": 62.6
    },
    {
      "step": 2,
      "color": { "hex": "#F7F5F1", "name": "Titanium White", "wholeParts": 9, "tintingStrength": 26 },
      "cumulative": { "#FFC500": 2, "#F7F5F1": 9 },
      "mixedColor": "#FBEE76",
      "matchResult": 64.3
    },
    {
      "step": 3,
      "color": { "hex": "#673020", "name": "Burnt Sienna", "wholeParts": 2, "tintingStrength": 61 },
      "cumulative": { "#FFC500": 2, "#F7F5F1": 9, "#673020": 2 },
      "mixedColor": "#D7B06F",
      "matchResult": 71.8
    },
    {
      "step": 4,
      "color": { "hex": "#19123F", "name": "Ultramarine Blue", "wholeParts": 6, "tintingStrength": 77 },
      "cumulative": { "#FFC500": 2, "#F7F5F1": 9, "#673020": 2, "#19123F": 6 },
      "mixedColor": "#718C76",
      "matchResult": 99.2
    }
  ],
  "diagramSteps": [
    { "step": 1, "cumulative": { "#FFC500": 2 }, "mixedColor": "#FFC500" },
    { "step": 2, "cumulative": { "#FFC500": 2, "#F7F5F1": 1 }, "mixedColor": "#FFD800" },
    { "step": 18, "cumulative": { "#FFC500": 2, "#F7F5F1": 9, "#673020": 2, "#19123F": 6 }, "mixedColor": "#718C76" }
  ],
  "omittedColors": []
}

diagramSteps is trimmed above. The real call returned 18 steps, one per part added.

Response fields

FieldTypeNotes
structurearrayEvery paint you sent, in the order you sent it, including the ones the recipe did not use.
structure[].hexstringThe hex you sent.
structure[].namestring or nullThe name you sent, or null if you sent none.
structure[].paint_idintegerEchoed back only for entries where you sent one. Absent otherwise.
structure[].countnumberFractional share of the mix, 0 to 1. The used entries sum to 1.
structure[].wholePartsintegerWhole parts for mixing by hand, each at most maxDropsCount. 0 means the paint is not in the practical recipe.
structure[].tintingStrengthinteger10 to 90, derived from the hex brightness. Darker reads as stronger. Only used when tintingStrengthMode is "adjusted".
mixedColorstringThe color you get from the wholeParts recipe, which is what a painter actually mixes.
matchResultnumberHow close mixedColor is to targetHex, 0 to 100.
ratioMixedColorstringThe color you get from the exact count ratios. This is the best the palette can do.
ratioMatchResultnumberHow close ratioMixedColor is to targetHex, 0 to 100.
practicalStepsarrayOne step per paint, ordered so the mix stays workable: weak and light paints first, strong and dark last. Each step carries the paint added, the running cumulative map of hex to parts, and the color and match after that step.
diagramStepsarrayThe same order at finer grain: the first paint all at once, then one part at a time. Built for animation. Each step has step, cumulative and mixedColor, with no matchResult.
omittedColorsarrayPaints the exact ratios wanted but rounding dropped, that is count > 0 and wholeParts === 0. Each entry is { hex, name }, with name falling back to the hex. A paint the optimizer never picked at all has count: 0 and does not appear here.

Conditional fields

Three fields appear only in some responses. Treat all three as optional.

suggestedPigments and bestAchievableMatch appear when the match falls below 95. bestAchievableMatch is the match a full pigment set would reach, so you can tell "your palette cannot do this" from "this color is hard for anyone". suggestedPigments names up to a few paints that would close the gap.

{
  "matchResult": 90.9,
  "ratioMatchResult": 91,
  "bestAchievableMatch": 99.7,
  "suggestedPigments": [
    { "hex": "#002E2A", "name": "Phthalo Green (Yellow Shade)", "predictedMatch": 100, "gain": 9.01 },
    { "hex": "#00A1A4", "name": "Cobalt Teal", "predictedMatch": 99.8, "gain": 8.84 },
    { "hex": "#00656B", "name": "Cobalt Turquoise", "predictedMatch": 95.6, "gain": 4.59 }
  ]
}

That response came from the same five Golden tubes aimed at #00B894, a green they cannot reach.

dilution appears for trace recipes: pale targets that need a dose of strong pigment too small for any parts ratio, such as a cream that wants 0.3% cadmium red in white. When it is present the recipe splits in two. structure[].wholeParts becomes a small concentrate you mix first, structure[].count stays the final proportions, and mixedColor and matchResult describe the marked ladder stop, which is the color you end up with.

FieldNotes
dilution.concentrateHexColor of the concentrate, that is the wholeParts mix.
dilution.diluent{ hex, name } of the paint to dilute into, normally the white in your palette.
dilution.ladderStops from the concentrate toward the diluent. Each has hex, concentrateShare (the fraction of the final mix that is concentrate) and matchResult.
dilution.targetStopIndexIndex into ladder of the stop that matches the target.
dilution.approxRatio{ concentrateParts, whiteParts }, a readable "1 part concentrate to N parts white".
dilution.plainBest{ mixedColor, matchResult }, what the parts-limited recipe would have given without the ladder.

Errors

{ "error": "Invalid input colors. At least 2 valid colors are required." }
{ "error": "Invalid target hexadecimal color." }
{ "error": "maxColorsCount and maxDropsCount must be positive whole numbers." }
{ "error": "Invalid engine: 2024. Must be '2023' or '2025'." }

A 503 with {"error":"Unmixer service is temporarily unavailable..."} means the mixing service is down. Retry.

Mix Colors

Mixes paints in the proportions you give and returns the result.

  • URL: /v1/mix-colors
  • Method: POST

Request body

FieldTypeRequiredNotes
colorsarrayyesNon-empty array of objects.
colors[].hexstringyes#RRGGBB.
colors[].countintegeryesParts of this paint. Non-negative whole number. Not optional: a missing count is a 400.
colors[].paint_idintegernoCatalog paint id. Read only by mixerMode: "pro" with engine: "2025", where it picks that paint's fitted curve. Ignored by the other paths.
colors[].tintingStrengthintegerno1 to 100. Read only when tintingStrengthMode is "adjusted".
mixerModestringno"basic" or "pro". Default "basic".
enginestringno"2023" or "2025". Default "2023". Only read when mixerMode is "pro".
tintingStrengthModestringno"uniform" or "adjusted". Default "uniform". Ignored when engine is "2025".

Example request

{
  "colors": [
    { "hex": "#F7F5F1", "count": 3, "paint_id": 3554 },
    { "hex": "#19123F", "count": 1, "paint_id": 3656 }
  ],
  "mixerMode": "pro",
  "engine": "2025"
}

Example response

{ "mixedColor": "#7FAEEB" }

The response is only the mixed color. Nothing else is returned.

Notes

  • Paints with count: 0 are skipped by the 2025 path. If every count is 0, the response is {"mixedColor":"#000000"}.
  • The 2025 path returns 400 when it cannot resolve mixing data for a color: {"error":"Could not get K/S data for color #XXXXXX: ..."}.
  • Enamel paints only mix with enamels of the same brand. In a mixed request the enamel entries fall back to plain hex resolution, so the result is less accurate than a pure enamel mix.

Search Paints

Searches the Trycolors paint catalog by keyword, by color similarity, or by filters, with pagination.

  • URL: /v1/paints/search
  • Method: POST

Request body

Every field is optional. A body with no search term and no filter returns the whole catalog sorted by name, page by page, so send at least one of q, color or a filter.

FieldTypeNotes
qstringKeyword. Searches paint name, slug, product number, brand name and pigment name or code.
colorstring#RRGGBB for similarity search.
thresholdnumberMaximum LAB distance for color. Default 10. Ignored without color.
brandstring or arrayBrand paths, for example "daniel-smith". A string may be comma separated.
mediumstring or arrayMedium names, for example "Watercolor".
lineIdstring, number or arrayProduct line ids.
transparencystring or arrayFor example "Transparent", "Semi-Transparent", "Opaque".
stainingstring or arrayFor example "Low Staining".
granulationstring or array"Yes" or "No".
colorGroupNamesstring or arrayOne of Red, Orange, Yellow, Green, Blue, Purple, Brown, Black, White, Grey. Singular, exactly as returned in colorGroupName.
sortBystring"relevance", "name" or "distance". Defaults to "relevance" with q, "distance" with color, "name" otherwise. "distance" without color falls back.
pageintegerDefault 1.
limitintegerResults per page. Default 20.

Example request

{
  "q": "quinacridone gold",
  "brand": ["daniel-smith"],
  "limit": 2
}

Example response

{
  "paints": [
    {
      "hex": "#BF7105",
      "paintName": "Quinacridone Gold",
      "sku": "",
      "brandName": "Daniel Smith",
      "brandId": 91,
      "lineName": "Original Oils",
      "mediumName": "Oil",
      "colorGroupName": "Black",
      "distance": null,
      "relevance": 205.76,
      "pigments": [
        { "id": 235, "code": "PO48", "name": "Quinacridone Burnt Orange", "colorIndex": null },
        { "id": 783, "code": "PY150", "name": "Nickel Azo Yellow", "colorIndex": null }
      ],
      "slug": "daniel-smith-original-oils-quinacridone-gold",
      "brandPath": "daniel-smith",
      "properties": {
        "transparency": "Transparent",
        "staining": "",
        "granulation": null,
        "lightfastness": "I – Excellent"
      }
    }
  ],
  "totalCount": 74
}

One of the two returned paints is shown.

Response fields

FieldTypeNotes
paints[].hexstringCatalog display color.
paints[].paintNamestringPaint name without the product number.
paints[].skustring or nullManufacturer product number. Can be null or an empty string when the catalog has none.
paints[].brandNamestring
paints[].brandIdinteger
paints[].lineNamestring or nullProduct line.
paints[].mediumNamestringFor example Oil, Watercolor, Acrylic.
paints[].colorGroupNamestring or nullCatalog color group.
paints[].distancenumber or nullLAB distance to color, rounded to 2 decimals. null when you did not send color.
paints[].relevancenumber or nullKeyword relevance score, rounded to 2 decimals. null when you did not send q.
paints[].pigmentsarray{ id, code, name, colorIndex } per pigment. code is the colour index code such as PO48, name is the pigment name, colorIndex is the CI number and is often null. Empty array when the paint has no pigment data.
paints[].slugstringPaint slug, used in the URL https://trycolors.com/paints/product/<slug>.
paints[].brandPathstringBrand slug. This is the value brand filters on.
paints[].propertiesobjecttransparency, staining, granulation ("Yes", "No" or null) and lightfastness. Any of them can be null or an empty string.
totalCountintegerMatches across all pages, not just this one.

The search response does not include the numeric paint id today. slug identifies a paint uniquely, so use it as your key. If you need paint_id values for /unmix-color or /mix-colors, contact support (opens in a new tab) with the slugs.

Similar Paints

Finds catalog paints close to a hex code. This endpoint reads an older catalog snapshot than /paints/search, so it returns fewer fields, and its brandId and seriesIds belong to that snapshot. They are not the ids /paints/search returns: Daniel Smith is brand 173 here and brand 91 there.

  • URL: /v1/similar-paints
  • Method: POST

Request body

FieldTypeRequiredNotes
hexstringyes#RRGGBB.
countintegernoNumber of paints to return. Default 10, capped at 10.
brandIdsarray of integersnoLimit to these brands. All active brands when omitted.
seriesIdsarray of integersnoLimit to these series. All active series when omitted.

Example request

{ "hex": "#FF0C13", "count": 3 }

Example response

{
  "paints": [
    { "hex": "#FF0C13", "paintName": "Red Orange", "sku": "203-F", "brandName": "1 Shot", "brandId": 1, "seriesName": "Fluorescent", "distance": 0 },
    { "hex": "#F70714", "paintName": "Cadmium Red Hue", "sku": "1420", "brandName": "Lukas", "brandId": 414, "seriesName": "Aquarell Studio", "distance": 1.8248703279757885 },
    { "hex": "#FF0015", "paintName": "Vermilion", "sku": "436", "brandName": "Marabu", "brandId": 424, "seriesName": "DecorGlass", "distance": 0.6234378309325322 }
  ]
}

Response fields

hex, paintName, sku, brandName, brandId, seriesName, distance. There is no lineName, no pigments, no slug and no paint id.

Results are ranked in RGB while distance is reported in LAB, so the array is not sorted by distance. Sort client side if the order matters. For anything beyond a quick lookup, prefer /paints/search with color and sortBy: "distance", which searches the current catalog and returns pigments and slugs.

Other paths

A few other paths under /v1/ serve trycolors.com itself. They are not part of the public API, are not supported for third-party use, and can change without notice. Every path not listed on this page should be treated as unavailable, including any you may find by guessing.