색상 API 설계: 색상 데이터용 REST 끝점
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.
변환이 필요한 디자인 도구, 대비를 확인하는 접근성 검사기, 명명된 색상 검색이 필요한 색상 선택기 구성 요소, 조화를 생성하는 팔레트 생성기 등 얼마나 많은 도구가 색상 데이터를 소비하는지 생각하기 전까지 색상 API는 틈새 시장으로 들립니다. 이러한 항목을 구축하거나 다른 개발자에게 색상 데이터를 제공하려면 잘 설계된 REST API가 필요합니다.
이 가이드는 ColorFYI에서 색상 변환기 및 대비 검사기를 구동하는 끝점을 구축한 사람의 관점에서 스키마 결정, 끝점 구조, 응답 형식, 오류 처리 및 속도 제한 등 전체 디자인 표면을 다룹니다.
색상 데이터를 위한 API 스키마 디자인
핵심 색상 개체
색상에는 16진수, RGB, HSL, CMYK, OKLCH 등 다양한 표현과 여러 메타데이터 필드가 있습니다. 표준 응답 개체에는 해당 항목이 모두 포함되어 클라이언트가 추가 요청 없이 필요한 형식을 사용할 수 있도록 해야 합니다.
{
"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
}
is_dark(휘도에서 파생됨)를 포함하면 클라이언트가 WCAG 휘도 계산을 직접 구현하지 않아도 됩니다. name 및 name_distance를 포함하면 클라이언트는 별도의 API 호출 없이 가장 가까운 이름의 색상을 표시할 수 있습니다.
명명 규칙
JSON 키에는 snake_case를 사용합니다(대부분의 REST API와 일치). 관련 필드를 평면화(rgb_r, rgb_g, rgb_b)하는 대신 객체(rgb, hsl, cmyk)에 중첩합니다. 중첩된 객체는 JavaScript에서 더 읽기 쉽고, 구조 해제가 더 쉬우며, 유형이 지정된 인터페이스에 깔끔하게 매핑됩니다.
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;
}
16진수 정규화
# 유무, 3자리 또는 6자리, 대문자 또는 소문자 등 다양한 형식의 16진수 색상을 허용하고 처리하기 전에 API 레이어에서 정규화합니다.
# Python/Django example
import re
def normalize_hex(raw: str) -> str:
"""Normalize hex input to 6-digit uppercase without #."""
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}")
# Expand 3-digit to 6-digit
if len(clean) == 3:
clean = ''.join(c * 2 for c in clean)
return clean
응답에 정규화된 형식을 반환합니다. 이렇게 하면 모호성이 제거되고 다운스트림 캐싱을 더 예측하기 쉬워집니다.
Hex/RGB/HSL 변환 끝점
GET /api/color/{hex}
기본 엔드포인트는 지정된 16진수 코드에 대한 전체 색상 개체를 반환합니다. 이는 색상 변환기가 모든 형식 필드를 동시에 채우는 데 사용하는 것입니다.
GET /api/color/FF5733
GET /api/color/%23FF5733 (URL-encoded #)
GET /api/color/f57 (3-digit, lowercase)
세 가지 모두 #FF5733에 대해 동일한 응답을 반환해야 합니다.
응답:
{
"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/변환
16진수가 아닐 수 있는 임의의 입력을 변환하는 경우 — CSS 색상 이름, RGB 트리플, HSL 값:
POST /api/convert
Content-Type: application/json
{
"input": "rgb(255, 87, 51)",
"from": "rgb"
}
응답: 동일한 풀 컬러 개체입니다.
hex, rgb, hsl, cmyk, oklch, name의 from 값을 허용합니다. 입력 형식이 선언된 from 유형과 일치하는지 확인하고 불일치 시 명확한 오류 메시지와 함께 422를 반환합니다.
Django ViewSet 예
# 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
색상 데이터를 적극적으로 캐시합니다. 주어진 16진수 코드는 항상 동일한 출력을 생성하므로 캐시의 수명이 매우 길어질 수 있습니다.
색상 검색 및 자동 완성 API
GET /api/검색
명명된 색상 검색은 색상 선택기 및 검색 필드에서 자동 완성 기능을 제공합니다. 쿼리 매개변수는 부분 색상 이름입니다.
GET /api/search?q=coral
GET /api/search?q=teal&limit=5
GET /api/search?q=%230d&source=css // Search by hex prefix
응답:
{
"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"
}
검색 구현 고려 사항
작은 이름의 색상 데이터베이스(최대 2,000개 항목)의 경우 퍼지 일치를 사용한 메모리 내 접두사 검색이 충분히 빠릅니다.
from difflib import SequenceMatcher
def search_colors(query: str, limit: int = 10) -> list[dict]:
q = query.lower().strip()
results = []
for color in NAMED_COLORS: # Pre-loaded list
name = color["name"].lower()
if q in name:
# Exact substring match: score based on position
pos = name.index(q)
score = 1.0 - (pos / len(name)) * 0.3
else:
# Fuzzy match
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]
항목이 10,000개보다 큰 데이터베이스의 경우 밀리초 미만 퍼지 검색을 위한 트라이그램 인덱싱 기능이 있는 PostgreSQL의 pg_trgm 확장을 사용하세요.
-- Enable extension
CREATE EXTENSION IF NOT EXISTS pg_trgm;
-- Add index
CREATE INDEX idx_color_name_trgm ON named_colors USING gin(name gin_trgm_ops);
-- Query
SELECT hex, name, source, similarity(name, 'coral') AS score
FROM named_colors
WHERE name % 'coral'
ORDER BY score DESC
LIMIT 10;
자동완성 최적화
입력 시 실시간 검색 자동 완성을 위해 짧은 대기 시간을 위해 최적화하세요.
- 클라이언트에서 디바운스 — 요청을 실행하기 전 최소 150ms입니다.
- 최소 쿼리 길이 설정 — 2자보다 짧은 쿼리를 거부합니다(빈 결과가 즉시 반환됨).
- 쿼리별로 결과 캐시 — 다른 사용자의 동일한 쿼리는 동일한 결과를 반환합니다.
Cache-Control: public, max-age=3600를 사용하세요. - 결과가 없으면 빠르게 반환 — 전체 퍼지 스캔을 실행하는 대신
{"results": [], "total": 0}로 즉시 응답합니다.
대비 검사 끝점
GET /api/대조
대비 API는 대비 검사기 뒤에 있는 엔진입니다. 두 개의 16진수 코드를 사용하고 각 레벨에 대한 통과/실패 상태와 함께 WCAG 명암비를 반환합니다.
GET /api/contrast?fg=FF5733&bg=FFFFFF
GET /api/contrast?fg=000000&bg=FFFFFF
응답:
{
"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
}
level 필드는 쌍이 달성하는 가장 높은 WCAG 수준("AAA", "AA", "AA Large" 또는 "Fail")을 반환합니다. WCAG 수준이 충족되면 passes 필드는 true입니다. 간단한 녹색/빨간색 표시기에 유용합니다.
대비 계산
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,
}
접근 가능한 대안 제안
유용한 확장: 배경과 대비가 맞지 않는 전경 색상이 있는 경우 WCAG AA를 달성하는 최소 밝기 조정을 제안합니다.
def suggest_accessible_foreground(fg_hex: str, bg_hex: str, target_ratio: float = 4.5) -> str:
"""Return a lightness-adjusted version of fg_hex that passes target_ratio."""
hsl = rgb_to_hsl(*hex_to_rgb(fg_hex))
bg_lum = relative_luminance(bg_hex)
is_dark_bg = bg_lum < 0.179
# Try adjusting lightness in the direction that increases contrast
step = -2 if is_dark_bg else 2 # Darken on light bg, lighten on dark bg
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
# Return maximum contrast if target is unreachable
return '#000000' if not is_dark_bg else '#FFFFFF'
Color API의 속도 제한
Color API에 속도 제한이 필요한 이유
Color API는 위험도가 낮은 것처럼 보일 수 있지만 다음과 같은 대상이 될 수 있습니다. - 열거 공격: 전체 색상 이름 데이터베이스를 수집하기 위해 1,670만 개의 16진수 코드를 모두 반복합니다. - 스크래핑: 변환 논리를 무료 계산 리소스로 사용합니다. - 악용: 다른 클라이언트의 서비스 수준을 저하시키는 단일 클라이언트의 대량 요청입니다.
실행 전략
Django API의 경우 django-ratelimit는 Redis 백엔드에 데코레이터 기반 속도 제한을 제공합니다.
# settings.py
RATELIMIT_USE_CACHE = 'default' # Uses Django's cache backend (Redis recommended)
# 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):
...
속도 제한 응답 형식
속도 제한이 초과되면 명확한 응답 본문 및 Retry-After 헤더와 함께 429 Too Many Requests를 반환합니다.
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
}
계층화된 비율 제한
합리적인 계층 구조:
| 계층 | 한도 | 열쇠 |
|---|---|---|
| 익명 | 요청 60개/분 | IP 주소 |
| API 키(무료) | 요청 300개/분 | API 키 |
| API 키(유료) | 요청 3000개/분 | API 키 |
API 키는 Authorization: Bearer {key} 또는 X-API-Key: {key}로 전달됩니다. 인증된 요청에 대한 비율 제한 키로 API 키를 사용합니다.
@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 checks both; the looser limit wins for authenticated requests
...
응답 형식 모범 사례
일관된 오류 스키마
모든 오류 응답(검증 실패, 속도 제한, 찾을 수 없음)은 동일한 스키마를 따라야 합니다.
{
"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"
}
| 필드 | 목적 |
|---|---|
error |
기계가 읽을 수 있는 오류 코드(snake_case) |
message |
사람이 읽을 수 있는 설명 |
field |
오류를 일으킨 입력 필드(유효성 검사 오류의 경우) |
value |
잘못된 값(디버깅에 도움) |
컬렉션 끝점에 대한 페이지 매김
limit 및 offset를 사용한 검색 엔드포인트:
{
"results": [...],
"pagination": {
"total": 47,
"limit": 10,
"offset": 0,
"next": "/api/search?q=blue&limit=10&offset=10",
"prev": null
}
}
클라이언트가 전체 결과 세트를 알지 못해도 페이지 매김 컨트롤을 렌더링할 수 있도록 항상 total를 포함하십시오.
버전 관리 전략
헤더가 아닌 URL 경로에서 API 버전을 지정하세요. URL 버전 관리는 명시적이며 CDN에서 캐시할 수 있으며 클라이언트가 사용자 정의 헤더를 설정할 필요가 없습니다.
/api/v1/color/FF5733 # Version 1
/api/v2/color/FF5733 # Version 2 (when breaking changes are needed)
주요 버전 출시 후 최소 1년 동안 이전 버전을 유지합니다. 이전 버전에서는 지원 중단 헤더를 사용하세요.
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"
CORS 구성
브라우저 클라이언트가 사용하는 공개 API의 경우:
# settings.py (using django-cors-headers)
CORS_ALLOWED_ORIGINS = [
"https://yoursite.com",
"https://app.yoursite.com",
]
# Or for a fully public API:
CORS_ALLOW_ALL_ORIGINS = True
# Always restrict methods and headers
CORS_ALLOW_METHODS = ['GET', 'POST', 'OPTIONS']
CORS_ALLOW_HEADERS = ['Content-Type', 'Authorization', 'X-API-Key']
캐싱 아키텍처
색상 데이터는 캐시 가능성이 높습니다. #FF5733의 색상 개체는 절대 변경되지 않습니다. 변환 수학은 결정적입니다. 그에 따라 캐시 전략을 구성하십시오.
| 엔드포인트 | 캐시 제어 | CDN TTL |
|---|---|---|
/api/color/{hex} |
public, max-age=86400, stale-while-revalidate=604800 |
1일 |
/api/search?q={q} |
public, max-age=3600 |
1시간 |
/api/contrast?fg=X&bg=Y |
public, max-age=86400 |
1일 |
/api/palette/{hex} |
public, max-age=86400 |
1일 |
조건부 요청에 대한 16진수 코드를 기반으로 ETag 헤더를 추가합니다.
response["ETag"] = f'"{hex_code}"'
response["Last-Modified"] = "Thu, 01 Jan 2026 00:00:00 GMT" # Static — content never changes
If-None-Match: "FF5733"를 보내는 클라이언트는 본문 없이 304 Not Modified 응답을 받습니다. 이는 반복 요청 시 대역폭을 절약합니다.
주요 내용
- 표준 색상 객체는 단일 응답에 모든 형식 표현(16진수, RGB, HSL, CMYK, OKLCH)을 포함해야 합니다. 클라이언트가 다른 형식을 얻기 위해 여러 요청을 하도록 만들지 마십시오.
- 16진수 입력을 적극적으로 정규화합니다.
#를 제거하고 3자리 숫자를 대문자로 확장합니다. 처리하기 전에 정규식으로 유효성을 검사하세요. - 대비 끝점은 모든 접근성 도구의 핵심입니다. 즉, WCAG 비율, 수준별 통과/실패 및 단일 응답의 최고 통과 수준을 반환합니다.
- 명명된 색상 검색은 소규모 데이터베이스에 대한 메모리 내 접두어 검색과 함께 안정적으로 작동합니다. 대규모에는 PostgreSQL
pg_trgm를 사용하십시오. - 인증되지 않은 요청에 대한 IP별 속도 제한(60/min이 합리적임) 상위 계층에는 API 키를 사용하세요.
- 모든 오류 응답은 최소한
error(기계 판독 가능) 및message(사람 판독 가능)와 동일한 스키마를 따라야 합니다. - 색상 데이터는 캐시 가능성이 높습니다. 24시간 CDN 캐시는 변환 엔드포인트에 적합합니다. 효율적인 캐시 무효화를 위해
ETag및stale-while-revalidate를 사용하십시오. - 색상 변환기 및 대비 검사기를 사용하여 이러한 API 엔드포인트가 실제로 작동하는지 확인하세요.