24. Pattern: Lookup List
The Lookup List pattern provides a dedicated, read-only API operation to retrieve dynamic lists of code-value pairs, human-readable labels, and associated metadata.
Instead of hardcoding frequently changing values as static enum definitions inside OpenAPI specifications or client codebases, this pattern exposes reference endpoints (e.g., GET /lookups/support-categories or GET /reference/currencies). This allows backend systems to introduce, modify, or deprecate options dynamically without breaking API contracts or requiring client application re-deployments.
24.1. Overview
In API design, enumerations (enum) work well for strictly fixed sets of values that drive core control flow (such as status: [DRAFT, SUBMITTED, PUBLISHED]). However, domain models often contain reference data that evolves over time—such as support ticket categories, product classification codes, country lists, or industry types.
If these values are defined as static OpenAPI enums, adding a new option requires:
- Updating the OpenAPI specification.
- Re-generating and publishing client SDKs.
- Deploying updates across all mobile, web, and third-party consumer applications.
The Lookup List pattern solves this by moving dynamic reference data into cacheable, read-only endpoints.
Typical examples:
GET /lookups/support-categories(Returns available support ticket topics with display labels)GET /reference/currencies(Returns ISO currency codes with symbols and formatting metadata)GET /reference/countries(Returns localized country names and dial codes)
These operations:
- Decouple dynamic dropdown choices from static client code
- Support localization (via headers like
Accept-Language) - Leverage HTTP caching (
Cache-Control,ETag) for sub-millisecond client performance
24.2. When to Use Lookup List
Use Lookup List when:
- Reference data changes more frequently than client software deployment cycles.
- e.g., Tax codes, merchant category codes, or seasonal promotion types added by business administrators.
- Values require human-readable labels or localization.
- e.g., Translating a category code (
"CAT_PAYMENT_ISSUE") into localized UI strings ("Payment & Billing Issue"in English vs."Problema de Pago y Facturación"in Spanish).
- e.g., Translating a category code (
- Options depend on metadata or status flags.
- e.g., Inactive options that must remain valid for historical record lookups but should be hidden from new entry dropdowns (
isActive: false).
- e.g., Inactive options that must remain valid for historical record lookups but should be hidden from new entry dropdowns (
- Form UI elements are driven dynamically by the backend.
- Client web or mobile applications render dropdown menus dynamically by querying lookup endpoints on load.
24.3. When NOT to Use Lookup List
Avoid Lookup List when:
- Values are strictly fixed and drive core application logic.
- e.g., HTTP methods (
GET,POST), boolean flags, or core state machine states (DRAFT,PUBLISHED). Standard OpenAPIenumfields are appropriate here.
- e.g., HTTP methods (
- The resource requires full CRUD lifecycle management.
- If clients need to create, update, or delete entries directly, use standard resource collections (e.g.,
/categories/{id}) rather than a read-only lookup list.
- If clients need to create, update, or delete entries directly, use standard resource collections (e.g.,
- The data set is massive or unbounded.
- Searchable datasets containing tens of thousands of records (e.g., customer directories or full address databases) should use Search/Query patterns with pagination rather than bulk lookup lists.
24.4. What the Pattern Looks Like
Below are HTTP request and response interaction flows demonstrating dynamic lookup retrieval, localization, and active-status filtering.
1. Standard Retrieval (GET /lookups/support-categories)
The client requests reference categories to populate a UI dropdown menu.
Request
GET /lookups/support-categories HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOi...
Accept-Language: en-US
Response
HTTP/1.1 200 OK
Cache-Control: public, max-age=86400, must-revalidate
ETag: "w/99a2b8"
Content-Type: application/json
{
"category": "support-categories",
"items": [
{
"code": "BILLING_ISSUE",
"label": "Billing & Invoicing",
"description": "Questions regarding charges, refunds, or payment methods.",
"displayOrder": 1,
"isActive": true
},
{
"code": "TECH_SUPPORT",
"label": "Technical Support",
"description": "Report bugs or platform access issues.",
"displayOrder": 2,
"isActive": true
},
{
"code": "FEATURE_REQ",
"label": "Feature Request",
"description": "Suggest new features or integrations.",
"displayOrder": 3,
"isActive": true
}
]
}
2. Localized Request (Accept-Language: es-ES)
The client requests the same lookup list with a Spanish localization header.
Request
GET /lookups/support-categories HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOi...
Accept-Language: es-ES
Response
HTTP/1.1 200 OK
Cache-Control: public, max-age=86400, must-revalidate
Vary: Accept-Language
ETag: "w/es-33d19"
Content-Type: application/json
{
"category": "support-categories",
"items": [
{
"code": "BILLING_ISSUE",
"label": "Facturación y Cobros",
"description": "Consultas sobre cargos, reembolsos o métodos de pago.",
"displayOrder": 1,
"isActive": true
},
{
"code": "TECH_SUPPORT",
"label": "Soporte Técnico",
"description": "Informar fallos o problemas de acceso.",
"displayOrder": 2,
"isActive": true
}
]
}
3. Filtering Inactive Codes (GET /lookups/support-categories?activeOnly=true)
Clients creating new records query only active options, while reporting dashboards query all codes including deprecated ones.
Request
GET /lookups/support-categories?activeOnly=true HTTP/1.1
Host: api.example.com
Response
Returns only entries where isActive: true.
24.5. Anti-Patterns to Avoid
1. Hardcoding frequently changing domain lists as OpenAPI enums
# Anti-Pattern: Specifying dynamic business options as an enum
type: string
enum: [BILLING_ISSUE, TECH_SUPPORT, FEATURE_REQ, ACCOUNT_RESET]
- Why it’s bad: Every time a new option is added, all OpenAPI specifications, SDKs, and client validators must be updated and re-deployed. Use standard
type: stringwith a reference lookup endpoint instead.
2. Returning key-only arrays without display labels
/* Anti-Pattern: Key-only lookup */
["BILLING_ISSUE", "TECH_SUPPORT", "FEATURE_REQ"]
- Why it’s bad: Returning raw code strings forces every client application (iOS, Android, Web) to duplicate translation maps and display logic. Return objects containing
code,label, anddescription.
3. Omitting Cache-Control headers on lookup endpoints
- Lookup data rarely changes minute-to-minute. Omitting
Cache-Controldirectives forces client applications to re-fetch identical data repeatedly, creating unnecessary server load. Always return long cache windows (e.g.,Cache-Control: public, max-age=86400) alongsideETagvalidators.
4. Polluting domain resources with nested lookup enumerations
- Embedding static lists of all possible options directly inside unrelated resource responses (e.g., returning all possible ticket categories inside every
GET /tickets/{id}response) bloats payload sizes. Keep lookup definitions in dedicated reference endpoints.
24.6. OpenAPI Example
A complete OpenAPI 3.0.3 specification illustrating how to document a Lookup List endpoint supporting category parameterization, caching headers, and localized items.
openapi: 3.0.3
info:
title: Reference Data API - Lookup List Pattern Example
version: 1.0.0
servers:
- url: https://api.example.com
paths:
/lookups/{category}:
get:
summary: Retrieve reference lookup values
description: Returns a cacheable list of valid codes, display labels, and metadata for a specified reference category.
tags: [Lookups]
parameters:
- in: path
name: category
required: true
schema:
type: string
enum: [support-categories, currencies, countries, tax-codes]
example: support-categories
- in: query
name: activeOnly
required: false
schema:
type: boolean
default: true
description: When true, filters out deprecated or inactive lookup codes.
- in: header
name: Accept-Language
required: false
schema:
type: string
example: "en-US"
description: Localizes response labels to the requested language.
responses:
'200':
description: Lookup values retrieved successfully
headers:
Cache-Control:
schema:
type: string
example: "public, max-age=86400, must-revalidate"
ETag:
schema:
type: string
example: '"w/99a2b8"'
Vary:
schema:
type: string
example: "Accept-Language"
content:
application/json:
schema:
$ref: '#/components/schemas/LookupListResponse'
components:
schemas:
LookupListResponse:
type: object
required:
- category
- items
properties:
category:
type: string
example: support-categories
items:
type: array
items:
$ref: '#/components/schemas/LookupItem'
LookupItem:
type: object
required:
- code
- label
- isActive
properties:
code:
type: string
example: BILLING_ISSUE
description: The stable programmatic code saved in database records.
label:
type: string
example: Billing & Invoicing
description: The localized human-readable display string for UIs.
description:
type: string
example: Questions regarding charges, refunds, or payment methods.
displayOrder:
type: integer
example: 1
isActive:
type: boolean
example: true
24.7. Visualizing Lookup List (Mermaid Diagram)
sequenceDiagram
autonumber
actor User as User / Browser
actor Client as Client App (Form UI)
participant CDN as Edge CDN / Cache
participant API as API Server
participant DB as Reference DB
Note over Client,DB: Phase 1: Dynamic Form Initialization
User->>Client: Opens "Create Support Ticket" Page
Client->>CDN: GET /lookups/support-categories (Accept-Language: es-ES)
alt Cached Copy Fresh in CDN
CDN-->>Client: 200 OK (From Cache: [Facturación, Soporte Técnico])
else Cache Expired or Miss
CDN->>API: GET /lookups/support-categories
API->>DB: Query active categories & es-ES translations
DB-->>API: Return category records
API-->>CDN: 200 OK (Cache-Control: max-age=86400, ETag: "w/es-33d19")
CDN-->>Client: 200 OK (JSON Lookup Payload)
end
Note over Client,User: Phase 2: Dynamic UI Rendering
Client->>Client: Populate <select> dropdown with code/label pairs
User->>Client: Selects "Facturación y Cobros" (code: "BILLING_ISSUE") & Submits
Note over Client,API: Phase 3: Domain Entity Creation
Client->>API: POST /tickets { "categoryCode": "BILLING_ISSUE", "subject": "..." }
API->>API: Validate "BILLING_ISSUE" against active lookup list
API-->>Client: 201 Created { id: "tkt_88192", status: "OPEN" }