Projetando uma API de Cores: Endpoints REST para Dados de Cores
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.
Uma API de cores parece nichada até você pensar em quantas ferramentas consomem dados de cores: ferramentas de design que precisam de conversão, verificadores de acessibilidade que verificam contraste, componentes de seletor de cores que precisam de pesquisa de cores nomeadas e geradores de paletas que produzem harmonias. Se você está construindo qualquer uma dessas, ou fornecendo dados de cores para outros desenvolvedores, precisa de uma API REST bem projetada.
Este guia cobre toda a superfície de design: decisões de esquema, estrutura de endpoint, formatos de resposta, tratamento de erros e limitação de taxa — da perspectiva de alguém que construiu os endpoints que alimentam o Conversor de Cores e o Verificador de Contraste no ColorFYI.
Design de Esquema da API para Dados de Cores
O Objeto Core de Cor
Uma cor tem múltiplas representações — hex, RGB, HSL, CMYK, OKLCH — e múltiplos campos de metadados. O objeto de resposta canônico deve incluir todos eles, permitindo que os clientes usem qualquer formato de que precisam sem fazer solicitações adicionais:
{
"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
}
Incluir is_dark (derivado de luminância) poupa os clientes de implementar o cálculo de luminância WCAG por conta própria. Incluir name e name_distance permite que os clientes mostrem a cor nomeada mais próxima sem uma chamada de API separada.
Convenções de Nomenclatura
Use snake_case para chaves JSON (consistente com a maioria das APIs REST). Aninhe campos relacionados em objetos (rgb, hsl, cmyk) em vez de achatá-los (rgb_r, rgb_g, rgb_b). Objetos aninhados são mais legíveis, mais fáceis de desestruturar em JavaScript e mapeiam bem para interfaces tipadas:
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;
}
Normalização de Hex
Aceite cores hex em múltiplos formatos — com ou sem #, 3 ou 6 dígitos, maiúsculas ou minúsculas — e normalize-as na camada da API antes de qualquer processamento:
# Exemplo Python/Django
import re
def normalize_hex(raw: str) -> str:
"""Normaliza entrada hex para 6 dígitos maiúsculos sem #."""
clean = raw.strip().lstrip('#').upper()
if not re.match(r'^[0-9A-F]{3}$|^[0-9A-F]{6}$', clean):
raise ValueError(f"Cor hex inválida: {raw!r}")
# Expande 3 dígitos para 6 dígitos
if len(clean) == 3:
clean = ''.join(c * 2 for c in clean)
return clean
Retorne a forma normalizada na resposta — isso elimina ambiguidade e torna o cache downstream mais previsível.
Endpoints de Conversão Hex/RGB/HSL
GET /api/color/{hex}
O endpoint principal retorna o objeto de cor completo para um código hex fornecido. É isso que o Conversor de Cores usa para preencher todos os campos de formato simultaneamente:
GET /api/color/FF5733
GET /api/color/%23FF5733 (# com codificação URL)
GET /api/color/f57 (3 dígitos, minúsculas)
Os três devem retornar a mesma resposta para #FF5733.
Resposta:
{
"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
Para converter entradas arbitrárias que podem não ser hex — nomes de cores CSS, triplas RGB, valores HSL:
POST /api/convert
Content-Type: application/json
{
"input": "rgb(255, 87, 51)",
"from": "rgb"
}
Resposta: Mesmo objeto de cor completo.
Aceite valores from de: hex, rgb, hsl, cmyk, oklch, name. Valide se o formato da entrada corresponde ao tipo from declarado e retorne 422 com uma mensagem de erro clara em caso de incompatibilidade.
Exemplo 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
Cache os dados de cor agressivamente — um determinado código hex sempre produz o mesmo resultado, então o cache pode ter vida muito longa.
API de Busca de Cores e Autocomplete
GET /api/search
A busca de cores nomeadas alimenta o autocomplete em seletores de cores e campos de pesquisa. O parâmetro de consulta é um nome de cor parcial:
GET /api/search?q=coral
GET /api/search?q=teal&limit=5
GET /api/search?q=%230d&source=css // Busca por prefixo hex
Resposta:
{
"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"
}
Considerações de Implementação de Busca
Para um banco de dados pequeno de cores nomeadas (~2.000 entradas), uma busca de prefixo em memória com correspondência fuzzy é suficientemente rápida:
from difflib import SequenceMatcher
def search_colors(query: str, limit: int = 10) -> list[dict]:
q = query.lower().strip()
results = []
for color in NAMED_COLORS: # Lista pré-carregada
name = color["name"].lower()
if q in name:
# Correspondência exata de substring: pontuação baseada na posição
pos = name.index(q)
score = 1.0 - (pos / len(name)) * 0.3
else:
# Correspondência fuzzy
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]
Para bancos de dados maiores que 10.000 entradas, use a extensão pg_trgm do PostgreSQL com indexação de trigrama para busca fuzzy em sub-milissegundos:
-- Habilitar extensão
CREATE EXTENSION IF NOT EXISTS pg_trgm;
-- Adicionar índice
CREATE INDEX idx_color_name_trgm ON named_colors USING gin(name gin_trgm_ops);
-- Consulta
SELECT hex, name, source, similarity(name, 'coral') AS score
FROM named_colors
WHERE name % 'coral'
ORDER BY score DESC
LIMIT 10;
Otimização de Autocomplete
Para um autocomplete de busca em tempo real enquanto digita, otimize para baixa latência:
- Debounce no cliente — mínimo de 150ms antes de disparar a solicitação.
- Defina um comprimento mínimo de consulta — rejeite consultas com menos de 2 caracteres (retorne resultados vazios imediatamente).
- Resultados de cache por consulta — a mesma consulta de diferentes usuários retorna o mesmo resultado. Use
Cache-Control: public, max-age=3600. - Retorne rapidamente em caso de sem resultados — responda imediatamente com
{"results": [], "total": 0}em vez de executar uma varredura fuzzy completa.
Endpoint de Verificação de Contraste
GET /api/contrast
A API de contraste é o motor por trás do Verificador de Contraste. Ela recebe dois códigos hex e retorna a taxa de contraste WCAG com status de aprovação/reprovação para cada nível:
GET /api/contrast?fg=FF5733&bg=FFFFFF
GET /api/contrast?fg=000000&bg=FFFFFF
Resposta:
{
"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
}
O campo level retorna o nível WCAG mais alto que o par atinge ("AAA", "AA", "AA Large" ou "Fail"). O campo passes é true se qualquer nível WCAG for atendido — útil para um indicador simples verde/vermelho.
Cálculo de 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,
}
Sugerindo Alternativas Acessíveis
Uma extensão útil: dado uma cor de primeiro plano que falha no contraste em relação ao fundo, sugira o ajuste mínimo de luminosidade que alcança o WCAG AA:
def suggest_accessible_foreground(fg_hex: str, bg_hex: str, target_ratio: float = 4.5) -> str:
"""Retorna uma versão ajustada de luminosidade de fg_hex que passa na target_ratio."""
hsl = rgb_to_hsl(*hex_to_rgb(fg_hex))
bg_lum = relative_luminance(bg_hex)
is_dark_bg = bg_lum < 0.179
# Tenta ajustar luminosidade na direção que aumenta o contraste
step = -2 if is_dark_bg else 2 # Escurece em fundo claro, clareia em fundo escuro
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
# Retorna contraste máximo se o alvo for inatingível
return '#000000' if not is_dark_bg else '#FFFFFF'
Limitação de Taxa para APIs de Cores
Por Que APIs de Cores Precisam de Limitação de Taxa
APIs de cores podem parecer de baixo risco, mas podem ser alvos de: - Ataques de enumeração: Iterar por todos os 16,7 milhões de códigos hex para coletar o banco de dados completo de nomes de cores. - Scraping: Usar sua lógica de conversão como um recurso computacional gratuito. - Abuso: Solicitações de alto volume de um único cliente que degradam o serviço para outros.
Estratégia de Implementação
Para uma API Django, o django-ratelimit fornece limitação de taxa baseada em decorator com backend Redis:
# settings.py
RATELIMIT_USE_CACHE = 'default' # Usa o backend de cache do Django (Redis recomendado)
# 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):
...
Formato de Resposta de Limite de Taxa
Quando um limite de taxa é excedido, retorne 429 Too Many Requests com um corpo de resposta claro e um cabeçalho 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": "Você excedeu o limite de taxa de 100 solicitações por minuto.",
"retry_after": 60
}
Limites de Taxa em Camadas
Uma estrutura em camadas sensata:
| Camada | Limite | Chave |
|---|---|---|
| Anônimo | 60 solicitações / minuto | Endereço IP |
| Chave de API (gratuita) | 300 solicitações / minuto | Chave de API |
| Chave de API (paga) | 3000 solicitações / minuto | Chave de API |
Chaves de API são passadas como Authorization: Bearer {chave} ou X-API-Key: {chave}. Use a chave de API como chave de limite de taxa para solicitações autenticadas:
@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 verifica ambos; o limite mais frouxo vence para solicitações autenticadas
...
Boas Práticas de Formato de Resposta
Esquema de Erro Consistente
Cada resposta de erro — falha de validação, limite de taxa, não encontrado — deve seguir o mesmo esquema:
{
"error": "invalid_hex",
"message": "O valor 'gggggg' não é uma cor hex válida. Era esperada uma string hex de 3 ou 6 dígitos.",
"field": "hex_code",
"value": "gggggg"
}
| Campo | Finalidade |
|---|---|
error |
Código de erro legível por máquina (snake_case) |
message |
Explicação legível por humano |
field |
Qual campo de entrada causou o erro (para erros de validação) |
value |
O valor inválido (ajuda na depuração) |
Paginação para Endpoints de Coleção
O endpoint de busca com limit e offset:
{
"results": [...],
"pagination": {
"total": 47,
"limit": 10,
"offset": 0,
"next": "/api/search?q=blue&limit=10&offset=10",
"prev": null
}
}
Sempre inclua total para que os clientes possam renderizar controles de paginação sem conhecer o conjunto completo de resultados.
Estratégia de Versionamento
Versione sua API no caminho da URL, não em um cabeçalho. O versionamento de URL é explícito, cacheável por CDNs e não requer que os clientes definam cabeçalhos personalizados:
/api/v1/color/FF5733 # Versão 1
/api/v2/color/FF5733 # Versão 2 (quando mudanças disruptivas forem necessárias)
Mantenha versões anteriores por pelo menos um ano após um lançamento de versão principal. Use cabeçalhos de depreciação em versões antigas:
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"
Configuração CORS
Para uma API pública consumida por clientes de navegador:
# settings.py (usando django-cors-headers)
CORS_ALLOWED_ORIGINS = [
"https://seusite.com",
"https://app.seusite.com",
]
# Ou para uma API totalmente pública:
CORS_ALLOW_ALL_ORIGINS = True
# Sempre restrinja métodos e cabeçalhos
CORS_ALLOW_METHODS = ['GET', 'POST', 'OPTIONS']
CORS_ALLOW_HEADERS = ['Content-Type', 'Authorization', 'X-API-Key']
Arquitetura de Cache
Os dados de cores são altamente cacheáveis. O objeto de cor para #FF5733 nunca mudará — a matemática de conversão é determinística. Estruture sua estratégia de cache de acordo:
| Endpoint | Cache-Control | TTL de CDN |
|---|---|---|
/api/color/{hex} |
public, max-age=86400, stale-while-revalidate=604800 |
1 dia |
/api/search?q={q} |
public, max-age=3600 |
1 hora |
/api/contrast?fg=X&bg=Y |
public, max-age=86400 |
1 dia |
/api/palette/{hex} |
public, max-age=86400 |
1 dia |
Adicione um cabeçalho ETag baseado no código hex para solicitações condicionais:
response["ETag"] = f'"{hex_code}"'
response["Last-Modified"] = "Thu, 01 Jan 2026 00:00:00 GMT" # Estático — o conteúdo nunca muda
Clientes que enviam If-None-Match: "FF5733" recebem uma resposta 304 Not Modified sem corpo — economizando largura de banda em solicitações repetidas.
Conclusões Principais
- O objeto de cor canônico deve incluir todas as representações de formato (hex, RGB, HSL, CMYK, OKLCH) em uma única resposta — nunca faça os clientes fazerem múltiplas solicitações para obter formatos diferentes.
- Normalize a entrada hex agressivamente: remova
#, expanda para 3 dígitos, converta para maiúsculas. Valide com regex antes de qualquer processamento. - O endpoint de contraste é o núcleo de qualquer ferramental de acessibilidade — retorne taxas WCAG, aprovação/reprovação por nível e o nível de aprovação mais alto em uma única resposta.
- A busca de cores nomeadas funciona de forma confiável com busca de prefixo em memória para bancos de dados pequenos; use
pg_trgmdo PostgreSQL para os maiores. - Limite a taxa por IP para solicitações não autenticadas (60/min é razoável); use chaves de API para camadas mais altas.
- Cada resposta de erro deve seguir o mesmo esquema com
error(legível por máquina) emessage(legível por humano) no mínimo. - Os dados de cores são altamente cacheáveis — o cache CDN de 24 horas é apropriado para endpoints de conversão; use
ETagestale-while-revalidatepara invalidação eficiente de cache. - Experimente o Conversor de Cores e o Verificador de Contraste para ver esses endpoints de API em ação.
Cores relacionadas
Marcas relacionadas
Ferramentas relacionadas
Conversor de cores
Converta entre os formatos de cor HEX, RGB, HSL, HSV, CMYK e OKLCH instantaneamente.
Verificador de contraste
Verifique as proporções de contraste de cores de acordo com as diretrizes WCAG 2.1. Teste a conformidade AA e AAA para texto normal e grande.