Concevoir une API de couleurs : endpoints REST pour les données de couleur
Embed This Widget
Add the script tag and a data attribute to embed this widget.
Embed via iframe for maximum compatibility.
<iframe src="https://colorfyi.com/iframe/entity//" width="420" height="400" frameborder="0" style="border:0;border-radius:10px;max-width:100%" loading="lazy"></iframe>
Paste this URL in WordPress, Medium, or any oEmbed-compatible platform.
https://colorfyi.com/entity//
Add a dynamic SVG badge to your README or docs.
[](https://colorfyi.com/entity//)
Use the native HTML custom element.
Une API de couleurs semble être un sujet de niche jusqu'à ce que vous réfléchissiez au nombre d'outils qui consomment des données de couleur : des outils de conception qui ont besoin de conversion, des vérificateurs d'accessibilité qui vérifient le contraste, des composants de sélection de couleur qui ont besoin de recherche de couleurs nommées et des générateurs de palettes qui produisent des harmonies. Si vous construisez l'un de ces outils, ou si vous fournissez des données de couleur à d'autres développeurs, vous avez besoin d'une API REST bien conçue.
Ce guide couvre l'ensemble de la surface de conception : décisions de schéma, structure des endpoints, formats de réponse, gestion des erreurs et limitation de débit — du point de vue de quelqu'un qui a construit les endpoints qui alimentent le Convertisseur de couleurs et le Vérificateur de contraste chez ColorFYI.
Conception du schéma d'API pour les données de couleur
L'objet couleur de base
Une couleur a plusieurs représentations — hexadécimale, RGB, HSL, CMYK, OKLCH — et plusieurs champs de métadonnées. L'objet de réponse canonique doit les inclure tous, permettant aux clients d'utiliser le format dont ils ont besoin sans faire de requêtes supplémentaires :
{
"hex": "#FF5733",
"rgb": {
"r": 255,
"g": 87,
"b": 51,
"css": "rgb(255, 87, 51)"
},
"hsl": {
"h": 11,
"s": 100,
"l": 60,
"css": "hsl(11, 100%, 60%)"
},
"hsv": {
"h": 11,
"s": 80,
"v": 100
},
"cmyk": {
"c": 0,
"m": 66,
"y": 80,
"k": 0
},
"oklch": {
"l": 0.63,
"c": 0.19,
"h": 27.5,
"css": "oklch(0.63 0.19 27.5)"
},
"luminance": 0.215,
"is_dark": false,
"name": "Coral Red",
"name_source": "color_name_database",
"name_distance": 0.04
}
Inclure is_dark (dérivé de la luminance) évite aux clients d'implémenter eux-mêmes le calcul de luminance WCAG. Inclure name et name_distance permet aux clients d'afficher la couleur nommée la plus proche sans un appel API séparé.
Conventions de nommage
Utilisez snake_case pour les clés JSON (cohérent avec la plupart des API REST). Imbriquez les champs liés dans des objets (rgb, hsl, cmyk) plutôt que de les aplatir (rgb_r, rgb_g, rgb_b). Les objets imbriqués sont plus lisibles, plus faciles à déstructurer en JavaScript et correspondent proprement aux interfaces typées :
interface ColorResponse {
hex: string;
rgb: { r: number; g: number; b: number; css: string };
hsl: { h: number; s: number; l: number; css: string };
oklch: { l: number; c: number; h: number; css: string };
luminance: number;
is_dark: boolean;
name: string | null;
name_source: string | null;
name_distance: number | null;
}
Normalisation hexadécimale
Acceptez les couleurs hexadécimales dans plusieurs formats — avec ou sans #, à 3 ou 6 chiffres, en majuscules ou minuscules — et normalisez-les dans la couche API avant tout traitement :
# Exemple Python/Django
import re
def normalize_hex(raw: str) -> str:
"""Normalise l'entrée hexadécimale en 6 chiffres majuscules sans #."""
clean = raw.strip().lstrip('#').upper()
if not re.match(r'^[0-9A-F]{3}$|^[0-9A-F]{6}$', clean):
raise ValueError(f"Invalid hex color: {raw!r}")
# Développe 3 chiffres en 6 chiffres
if len(clean) == 3:
clean = ''.join(c * 2 for c in clean)
return clean
Retournez le formulaire normalisé dans la réponse — cela élimine l'ambiguïté et rend la mise en cache en aval plus prévisible.
Endpoints de conversion Hex/RGB/HSL
GET /api/color/{hex}
L'endpoint principal retourne l'objet couleur complet pour un code hexadécimal donné. C'est ce que le Convertisseur de couleurs utilise pour remplir simultanément tous les champs de format :
GET /api/color/FF5733
GET /api/color/%23FF5733 (# encodé en URL)
GET /api/color/f57 (3 chiffres, minuscules)
Les trois doivent retourner la même réponse pour #FF5733.
Réponse :
{
"hex": "#FF5733",
"rgb": { "r": 255, "g": 87, "b": 51, "css": "rgb(255, 87, 51)" },
"hsl": { "h": 11, "s": 100, "l": 60, "css": "hsl(11, 100%, 60%)" },
"cmyk": { "c": 0, "m": 66, "y": 80, "k": 0 },
"oklch": { "l": 0.63, "c": 0.19, "h": 27.5, "css": "oklch(0.63 0.19 27.5)" },
"luminance": 0.215,
"is_dark": false,
"name": "Coral Red",
"name_source": "css_extended",
"name_distance": 0.08
}
POST /api/convert
Pour convertir des entrées arbitraires qui peuvent ne pas être hexadécimales — noms de couleurs CSS, triplets RGB, valeurs HSL :
POST /api/convert
Content-Type: application/json
{
"input": "rgb(255, 87, 51)",
"from": "rgb"
}
Réponse : Même objet couleur complet.
Acceptez les valeurs from de : hex, rgb, hsl, cmyk, oklch, name. Validez que le format d'entrée correspond au type from déclaré et retournez un 422 avec un message d'erreur clair en cas de non-correspondance.
Exemple de ViewSet Django
# apps/api/views.py
from django.http import JsonResponse
from django.views import View
from apps.colors.engine import ColorEngine
class ColorDetailView(View):
def get(self, request, hex_code: str):
try:
normalized = normalize_hex(hex_code)
except ValueError as e:
return JsonResponse(
{"error": "invalid_hex", "message": str(e)},
status=422
)
engine = ColorEngine(normalized)
data = {
"hex": f"#{normalized}",
"rgb": engine.to_rgb_dict(),
"hsl": engine.to_hsl_dict(),
"cmyk": engine.to_cmyk_dict(),
"oklch": engine.to_oklch_dict(),
"luminance": engine.relative_luminance(),
"is_dark": engine.is_dark(),
"name": engine.nearest_name(),
"name_source": engine.nearest_name_source(),
"name_distance": engine.nearest_name_distance(),
}
response = JsonResponse(data)
response["Cache-Control"] = "public, max-age=86400, stale-while-revalidate=604800"
return response
Mettez en cache les données de couleur de manière agressive — un code hexadécimal donné produit toujours la même sortie, donc le cache peut être de très longue durée.
API de recherche de couleurs et d'autocomplétion
GET /api/search
La recherche de couleurs nommées alimente l'autocomplétion dans les sélecteurs de couleur et les champs de recherche. Le paramètre de requête est un nom de couleur partiel :
GET /api/search?q=coral
GET /api/search?q=teal&limit=5
GET /api/search?q=%230d&source=css // Recherche par préfixe hexadécimal
Réponse :
{
"results": [
{
"hex": "#FF7F50",
"name": "Coral",
"source": "css_named",
"score": 1.0
},
{
"hex": "#FF6B6B",
"name": "Light Coral",
"source": "css_extended",
"score": 0.85
},
{
"hex": "#E8735A",
"name": "Terra Cotta",
"source": "crayola",
"score": 0.62
}
],
"total": 3,
"query": "coral"
}
Considérations d'implémentation de la recherche
Pour une petite base de données de couleurs nommées (~2 000 entrées), une recherche de préfixe en mémoire avec correspondance floue est suffisamment rapide :
from difflib import SequenceMatcher
def search_colors(query: str, limit: int = 10) -> list[dict]:
q = query.lower().strip()
results = []
for color in NAMED_COLORS: # Liste pré-chargée
name = color["name"].lower()
if q in name:
# Correspondance de sous-chaîne exacte : score basé sur la position
pos = name.index(q)
score = 1.0 - (pos / len(name)) * 0.3
else:
# Correspondance floue
score = SequenceMatcher(None, q, name).ratio()
if score < 0.5:
continue
results.append({**color, "score": round(score, 3)})
results.sort(key=lambda x: x["score"], reverse=True)
return results[:limit]
Pour les bases de données de plus de 10 000 entrées, utilisez l'extension pg_trgm de PostgreSQL avec indexation trigramme pour une recherche floue en sous-milliseconde :
-- Activer l'extension
CREATE EXTENSION IF NOT EXISTS pg_trgm;
-- Ajouter l'index
CREATE INDEX idx_color_name_trgm ON named_colors USING gin(name gin_trgm_ops);
-- Requête
SELECT hex, name, source, similarity(name, 'coral') AS score
FROM named_colors
WHERE name % 'coral'
ORDER BY score DESC
LIMIT 10;
Optimisation de l'autocomplétion
Pour une autocomplétion en direct lors de la saisie, optimisez pour une faible latence :
- Debounce côté client — 150 ms minimum avant de déclencher la requête.
- Définir une longueur minimale de requête — rejeter les requêtes de moins de 2 caractères (retourner immédiatement des résultats vides).
- Mettre en cache les résultats par requête — la même requête de différents utilisateurs retourne le même résultat. Utilisez
Cache-Control: public, max-age=3600. - Répondre rapidement en l'absence de résultats — répondre immédiatement avec
{"results": [], "total": 0}plutôt que d'exécuter un scan flou complet.
Endpoint de vérification du contraste
GET /api/contrast
L'API de contraste est le moteur derrière le Vérificateur de contraste. Elle prend deux codes hexadécimaux et retourne le rapport de contraste WCAG avec le statut réussite/échec pour chaque niveau :
GET /api/contrast?fg=FF5733&bg=FFFFFF
GET /api/contrast?fg=000000&bg=FFFFFF
Réponse :
{
"ratio": 3.02,
"ratio_formatted": "3.02:1",
"foreground": {
"hex": "#FF5733",
"luminance": 0.215
},
"background": {
"hex": "#FFFFFF",
"luminance": 1.0
},
"wcag": {
"aa_normal": false,
"aa_large": true,
"aaa_normal": false,
"aaa_large": false
},
"level": "AA Large",
"passes": true
}
Le champ level retourne le niveau WCAG le plus élevé atteint par la paire ("AAA", "AA", "AA Large" ou "Fail"). Le champ passes est true si un niveau WCAG est atteint — utile pour un indicateur vert/rouge simple.
Calcul du contraste
def relative_luminance(hex_code: str) -> float:
r, g, b = hex_to_rgb(hex_code)
def linearize(v: int) -> float:
s = v / 255
return s / 12.92 if s <= 0.04045 else ((s + 0.055) / 1.055) ** 2.4
return 0.2126 * linearize(r) + 0.7152 * linearize(g) + 0.0722 * linearize(b)
def contrast_ratio(hex1: str, hex2: str) -> float:
L1 = relative_luminance(hex1)
L2 = relative_luminance(hex2)
lighter = max(L1, L2)
darker = min(L1, L2)
return round((lighter + 0.05) / (darker + 0.05), 2)
def wcag_result(ratio: float) -> dict:
return {
"aa_normal": ratio >= 4.5,
"aa_large": ratio >= 3.0,
"aaa_normal": ratio >= 7.0,
"aaa_large": ratio >= 4.5,
}
Suggérer des alternatives accessibles
Une extension utile : étant donné une couleur de premier plan qui échoue au contraste par rapport au fond, suggérez l'ajustement de luminosité minimal qui atteint WCAG AA :
def suggest_accessible_foreground(fg_hex: str, bg_hex: str, target_ratio: float = 4.5) -> str:
"""Retourne une version de fg_hex ajustée en luminosité qui passe target_ratio."""
hsl = rgb_to_hsl(*hex_to_rgb(fg_hex))
bg_lum = relative_luminance(bg_hex)
is_dark_bg = bg_lum < 0.179
# Essayez d'ajuster la luminosité dans la direction qui augmente le contraste
step = -2 if is_dark_bg else 2 # Assombrir sur fond clair, éclaircir sur fond sombre
current_l = hsl[2]
for _ in range(50):
current_l = max(0, min(100, current_l + step))
adjusted_hex = hsl_to_hex(hsl[0], hsl[1], current_l)
ratio = contrast_ratio(adjusted_hex, bg_hex)
if ratio >= target_ratio:
return adjusted_hex
# Retourner le contraste maximum si la cible est inaccessible
return '#000000' if not is_dark_bg else '#FFFFFF'
Limitation de débit pour les API de couleurs
Pourquoi les API de couleurs ont besoin de limitation de débit
Les API de couleurs peuvent sembler à faible risque, mais elles peuvent être des cibles pour : - Attaques d'énumération : Itérer à travers les 16,7 millions de codes hexadécimaux pour collecter la base de données complète des couleurs nommées. - Scraping : Utiliser votre logique de conversion comme ressource computationnelle gratuite. - Abus : Requêtes à volume élevé d'un seul client qui dégradent le service pour les autres.
Stratégie d'implémentation
Pour une API Django, django-ratelimit fournit une limitation de débit basée sur des décorateurs avec un backend Redis :
# settings.py
RATELIMIT_USE_CACHE = 'default' # Utilise le backend de cache de Django (Redis recommandé)
# views.py
from django_ratelimit.decorators import ratelimit
@ratelimit(key='ip', rate='100/m', method='GET', block=True)
def color_detail_view(request, hex_code: str):
...
Format de réponse de limite de débit
Lorsqu'une limite de débit est dépassée, retournez 429 Too Many Requests avec un corps de réponse clair et un en-tête Retry-After :
HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1708012345
Content-Type: application/json
{
"error": "rate_limit_exceeded",
"message": "You have exceeded the rate limit of 100 requests per minute.",
"retry_after": 60
}
Limites de débit à plusieurs niveaux
Une structure à plusieurs niveaux sensée :
| Niveau | Limite | Clé |
|---|---|---|
| Anonyme | 60 requêtes / minute | Adresse IP |
| Clé API (gratuite) | 300 requêtes / minute | Clé API |
| Clé API (payante) | 3 000 requêtes / minute | Clé API |
Les clés API sont transmises sous la forme Authorization: Bearer {clé} ou X-API-Key: {clé}. Utilisez la clé API comme clé de limite de débit pour les requêtes authentifiées :
@ratelimit(key='header:x-api-key', rate='300/m', method='GET')
@ratelimit(key='ip', rate='60/m', method='GET')
def color_detail_view(request, hex_code: str):
# django-ratelimit vérifie les deux ; la limite la plus souple gagne pour les requêtes authentifiées
...
Meilleures pratiques de format de réponse
Schéma d'erreur cohérent
Chaque réponse d'erreur — échec de validation, limite de débit, non trouvé — doit suivre le même schéma :
{
"error": "invalid_hex",
"message": "The value 'gggggg' is not a valid hex color. Expected a 3 or 6 digit hex string.",
"field": "hex_code",
"value": "gggggg"
}
| Champ | Objectif |
|---|---|
error |
Code d'erreur lisible par la machine (snake_case) |
message |
Explication lisible par l'humain |
field |
Quel champ d'entrée a causé l'erreur (pour les erreurs de validation) |
value |
La valeur invalide (aide au débogage) |
Pagination pour les endpoints de collection
L'endpoint de recherche avec limit et offset :
{
"results": [...],
"pagination": {
"total": 47,
"limit": 10,
"offset": 0,
"next": "/api/search?q=blue&limit=10&offset=10",
"prev": null
}
}
Incluez toujours total pour que les clients puissent afficher les contrôles de pagination sans connaître l'ensemble complet de résultats.
Stratégie de versionnage
Versionnez votre API dans le chemin URL, pas dans un en-tête. Le versionnage par URL est explicite, mis en cache par les CDN et ne nécessite pas que les clients définissent des en-têtes personnalisés :
/api/v1/color/FF5733 # Version 1
/api/v2/color/FF5733 # Version 2 (quand des changements incompatibles sont nécessaires)
Maintenez les versions précédentes pendant au moins un an après une version majeure. Utilisez des en-têtes de dépréciation sur les anciennes versions :
Deprecation: Sun, 01 Jan 2026 00:00:00 GMT
Sunset: Sun, 01 Jan 2027 00:00:00 GMT
Link: </api/v2/color/FF5733>; rel="successor-version"
Configuration CORS
Pour une API publique consommée par des clients navigateur :
# settings.py (en utilisant django-cors-headers)
CORS_ALLOWED_ORIGINS = [
"https://yoursite.com",
"https://app.yoursite.com",
]
# Ou pour une API entièrement publique :
CORS_ALLOW_ALL_ORIGINS = True
# Restreignez toujours les méthodes et les en-têtes
CORS_ALLOW_METHODS = ['GET', 'POST', 'OPTIONS']
CORS_ALLOW_HEADERS = ['Content-Type', 'Authorization', 'X-API-Key']
Architecture de mise en cache
Les données de couleur sont hautement cachables. L'objet couleur pour #FF5733 ne changera jamais — les calculs de conversion sont déterministes. Structurez votre stratégie de cache en conséquence :
| Endpoint | Cache-Control | TTL CDN |
|---|---|---|
/api/color/{hex} |
public, max-age=86400, stale-while-revalidate=604800 |
1 jour |
/api/search?q={q} |
public, max-age=3600 |
1 heure |
/api/contrast?fg=X&bg=Y |
public, max-age=86400 |
1 jour |
/api/palette/{hex} |
public, max-age=86400 |
1 jour |
Ajoutez un en-tête ETag basé sur le code hexadécimal pour les requêtes conditionnelles :
response["ETag"] = f'"{hex_code}"'
response["Last-Modified"] = "Thu, 01 Jan 2026 00:00:00 GMT" # Statique — le contenu ne change jamais
Les clients qui envoient If-None-Match: "FF5733" obtiennent une réponse 304 Not Modified sans corps — économisant de la bande passante sur les requêtes répétées.
Points clés
- L'objet couleur canonique doit inclure toutes les représentations de format (hex, RGB, HSL, CMYK, OKLCH) dans une seule réponse — ne faites jamais faire plusieurs requêtes aux clients pour obtenir différents formats.
- Normalisez l'entrée hexadécimale de manière agressive : supprimez
#, développez 3 chiffres, mettez en majuscules. Validez avec une regex avant tout traitement. - L'endpoint de contraste est le cœur de tout outil d'accessibilité — retournez les rapports WCAG, le résultat réussite/échec par niveau et le niveau de passage le plus élevé dans une seule réponse.
- La recherche de couleurs nommées fonctionne de manière fiable avec une recherche de préfixe en mémoire pour les petites bases de données ; utilisez
pg_trgmde PostgreSQL pour les grandes. - Limitez le débit par IP pour les requêtes non authentifiées (60/min est raisonnable) ; utilisez des clés API pour des niveaux supérieurs.
- Chaque réponse d'erreur doit suivre le même schéma avec
error(lisible par la machine) etmessage(lisible par l'humain) au minimum. - Les données de couleur sont hautement cachables — un cache CDN de 24 heures est approprié pour les endpoints de conversion ; utilisez
ETagetstale-while-revalidatepour une invalidation de cache efficace. - Essayez le Convertisseur de couleurs et le Vérificateur de contraste pour voir ces endpoints API en action.
Couleurs associées
Marques associées
Outils associés
Convertisseur de couleurs
Convertissez instantanément entre les formats de couleur HEX, RGB, HSL, HSV, CMYK et OKLCH.
Vérificateur de contraste
Vérifiez les ratios de contraste des couleurs selon les directives WCAG 2.1. Testez la conformité AA et AAA pour le texte normal et le grand texte.