チュートリアル

色 API の設計:色データの REST エンドポイント

5分で読める

色 API はニッチに聞こえるのは、変換を必要とするデザインツール、対比を検証するアクセシビリティ チェッカー、名前の色検索が必要な色ピッカーコンポーネント、調和を生成するパレットジェネレーターなど、色データを消費する多くのツールについて考えるまでです。これらのいずれかを構築する場合、または他の開発者に色データを提供する場合、設計した REST API が必要です。

このガイドでは、完全な設計表面をカバーしています:スキーマの決定、エンドポイント構造、応答形式、エラー処理、およびレート制限—ColorFYI で Color ConverterContrast Checkerを強化するエンドポイントを構築した誰かの見方から。


色データ向けの 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 輝度計算を自分で実装するのを保存します。namename_distance を含めるで、クライアントは追加の API 呼び出しなしで最も近い名前付きの色を表示できます。

ネーミング規則

JSON キーに snake_case を使用(ほとんどの REST API と一貫性がある)。関連フィールドを rgbhslcmyk などのオブジェクトにネスト(フラット化ではなく)(rgb_rrgb_grgb_bの代わりに) 。ネストされたオブジェクトはより読みやすく、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進数正規化

複数の形式で 16 進色を受け入れ— # または # なしで、3 桁または 6 桁、大文字または小文字—そして API レイヤーでそれらを正規化してからあらゆる処理:

# Python/Django の例
import re

def normalize_hex(raw: str) -> str:
    """16 進の入力を 6 桁の大文字に正規化します # なし。"""
    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}")
    # 3 桁を 6 桁に展開
    if len(clean) == 3:
        clean = ''.join(c * 2 for c in clean)
    return clean

正規化されたフォームを応答で返す—これはあいまいさを排除し、ダウンストリームキャッシングをより予測可能にします。


16進数/RGB/HSL 変換エンドポイント

GET /api/color/{hex}

プライマリーエンドポイントは、与えられた 16 進コードの完全な色オブジェクトを返します。これが Color Converterすべてのフォーマットフィールドを同時に入力するために使用するところです:

GET /api/color/FF5733
GET /api/color/%23FF5733   (URL エンコード #)
GET /api/color/f57          (3 桁、小文字)

3 つすべてが #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/convert

16 進数ではない可能性のある任意の入力を変換する場合—CSS 色の名前、RGB トリプル、HSL 値:

POST /api/convert
Content-Type: application/json

{
  "input": "rgb(255, 87, 51)",
  "from": "rgb"
}

応答 :同じ完全な色オブジェクト。

hexrgbhslcmykoklchnamefrom 値を受け入れます。入力形式が宣言された 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/search

名前付き色検索で、色ピッカーとサーチフィールドでオートコンプリートを強化します。クエリパラメーターは、部分色の名前:

GET /api/search?q=coral
GET /api/search?q=teal&limit=5
GET /api/search?q=%230d&source=css   // 16進プレフィックスで検索

応答:

{
  "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:  # 事前読み込みリスト
        name = color["name"].lower()
        if q in name:
            # 正確な部分文字列マッチ:位置に基づいてスコア
            pos = name.index(q)
            score = 1.0 - (pos / len(name)) * 0.3
        else:
            # ファジーマッチ
            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 拡張を三グラム インデックスで使用して、ミリ秒未満のあいまい検索を使用:

-- 拡張を有効にする
CREATE EXTENSION IF NOT EXISTS pg_trgm;

-- インデックスを追加
CREATE INDEX idx_color_name_trgm ON named_colors USING gin(name gin_trgm_ops);

-- クエリ
SELECT hex, name, source, similarity(name, 'coral') AS score
FROM named_colors
WHERE name % 'coral'
ORDER BY score DESC
LIMIT 10;

オートコンプリート最適化

ライブ検索-入力中のオートコンプリートの場合、低レイテンシ向けの最適化:

  1. クライアント側でデバウンス —リクエストを発火する前に最小150ms。
  2. 最小クエリ長を設定 —2 文字より短いクエリを拒否(すぐに空の結果を返します)。
  3. クエリで結果をキャッシュ —同じクエリが異なるユーザーから同じ結果を返します。Cache-Control: public, max-age=3600 を使用します。
  4. 結果がない場合は迅速に返す —完全なあいまいスキャンを実行するのではなく、すぐに {"results": [], "total": 0} で応答します。

コントラスト チェック エンドポイント

GET /api/contrast

コントラスト API は Contrast Checkerの背後のエンジンです。2つの 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")を返します。passes フィールドは、いずれかの WCAG レベルが達成された場合 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:
    """ターゲット比に合格する輝度調整バージョン fg_hex を返す。"""
    hsl = rgb_to_hsl(*hex_to_rgb(fg_hex))
    bg_lum = relative_luminance(bg_hex)
    is_dark_bg = bg_lum < 0.179

    # コントラストを増加させる方向で輝度を調整してみてください
    step = -2 if is_dark_bg else 2  # 薄い背景を暗くする、暗い背景を明るくする
    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 '#000000' if not is_dark_bg else '#FFFFFF'

色 API のレート制限

色 API がレート制限を必要とする理由

色 API はローリスクに見えるかもしれませんが、ターゲットになる可能性があります: - 列挙攻撃 :すべての 1670 万の 16 進コードを通じて反復処理して、完全な色の名前データベースを収穫。 - スクレイピング :無料の計算リソースとして変換ロジックを使用。 - 悪用 :単一のクライアントからの大量のリクエスト、その他のサービスの品質を低下させます。

実装戦略

Django API の場合、django-ratelimit は Redis バックエンドを持つデコレータベースのレート制限を提供します:

# settings.py
RATELIMIT_USE_CACHE = 'default'  # Django のキャッシュバックエンド(Redis 推奨)を使用

# 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):
    ...

レート制限応答形式

レート制限が超過されると、429 Too Many Requests で明確な応答本体と 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
}

段階的なレート制限

判度的な構造:

ティア 制限 キー
匿名 毎分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 は両方をチェックします。認証されたリクエストの場合、より安い制限が勝ちます
    ...

応答形式ベストプラクティス

一貫性のあるエラースキーマ

すべてのエラー応答—検証失敗、レート制限、見つからない—同じスキーマに従う必要があります:

{
  "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 無効な値(デバッグに役立つ)

コレクションエンドポイントのページネーション

limitoffset を含む検索エンドポイント:

{
  "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    # バージョン 1
/api/v2/color/FF5733    # バージョン 2(破壊的な変更が必要な場合)

主要なバージョンリリース後、少なくとも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 (django-cors-headers を使用)
CORS_ALLOWED_ORIGINS = [
    "https://yoursite.com",
    "https://app.yoursite.com",
]

# またはパブリック API の場合:
CORS_ALLOW_ALL_ORIGINS = True

# 常にメソッドとヘッダーを制限
CORS_ALLOW_METHODS = ['GET', 'POST', 'OPTIONS']
CORS_ALLOW_HEADERS = ['Content-Type', 'Authorization', 'X-API-Key']

キャッシングアーキテクチャ

色データは高度にキャッシュ可能です。#FF5733 の色オブジェクトは決して変わりません—変換数学は決定的です。それに応じてキャッシング戦略を構造化:

エンドポイント Cache-Control 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"  # 静的—コンテンツは変わることはない

If-None-Match: "FF5733" を送信するクライアントは、本体なしで 304 Not Modified 応答を取得—繰り返しリクエストで帯域幅を節約します。


重要なポイント

  • 正規色オブジェクトは、すべての形式表現(16 進数、RGB、HSL、CMYK、OKLCH)を1つの応答に含める必要があります—クライアントが異なる形式を取得するために複数のリクエストを作成しないでください。
  • 積極的に 16 進入力を正規化:# をストリップ、3 桁を展開、大文字。処理前に正規表現で検証します。
  • コントラストエンドポイントは任意のアクセシビリティツールの中核です—WCAG 比、レベルごとの合格/不合格、および1つの応答での最高通過レベルを返します。
  • 名前付き色検索は小さなデータベース(現在の文字列検索で信頼できに動作);大規模なデータベースに PostgreSQL pg_trgm を使用します。
  • 認証されていないリクエストの IP によってレート制限(毎分60回が妥当);API キーを使用して高いティア。
  • すべてのエラー応答は同じスキーマに従う必要があります。最小限 error(マシンが読み取り可能)と message(人間が読み取り可能)を持つ。
  • 色データは高度にキャッシュ可能です— 24 時間の CDN キャッシュは変換エンドポイントに適切です。効率的なキャッシュの無効化に ETagstale-while-revalidate を使用します。
  • Color ConverterContrast Checkerを試してください。これらの API エンドポイントが動作中に見ることができます。

関連カラー

関連ブランド

関連ツール