How to Write Detailed Design for APIs with Search Functionality
In the previous article, we explored how to write API documentation using the API Blueprint format. This article will guide you through writing detailed design for search APIs systematically, from requirement analysis to implementation details. The principles and design patterns shared have been validated through multiple real-world projects and can be directly applied to your work.
Structure of Detailed Design for Search APIs
1. API Name
Have you ever been in a meeting where people describe APIs like: "Um... the API that gets the list of orders by user... on that user detail screen..."? I have, and it's really... exhausting.
The reality is that API naming is often overlooked, but it has an enormous impact on team productivity. Let's compare:
❌ "The API that gets the list of users for the user management screen", "The API that gets the list of orders by user"
→ Verbose, not suitable for design documents, especially when writing it in FE detail specs
❌ "API GET /users", "API GET /users/{id}/orders"
→ Sounds professional but hard to visualize in speech, listeners can't immediately understand what this API does
✅ "API userSearch", "API orderSearchByUserId"
→ Concise, immediately understandable functionality, easy to reference in other documents
Naming Rules:
Consistency within a project is crucial. Therefore, I recommend establishing naming conventions from the start of the project. You can freely create these rules as long as team members are comfortable with them.
Below are the naming principles I use for reference:
- APIs with general search functionality: [Object] + Search/List
Examples:
- `productSearch` - Search products
- `orderSearch` - Search orders
- `orderUpdateHistoryList` - List of order update history
- `productPriceHistoryList` - List of product price history - APIs that retrieve data lists based on a foreign key ID: [Object] + SearchBy/GetBy/ListBy + [Foreign Key]
Examples:
- `orderGetByUserId` - Get orders associated with user ID
- `productSearchByCompanyId` - Search products associated with company ID
Once the team is familiar with this naming convention, just saying "userSearch" and everyone immediately understands which API it is, what it does, no further explanation needed. So much meeting time saved!
2. HTTP Method
The two most common methods used for search APIs are GET and POST.
When using GET, search parameters (or criteria) will be visible in the URL as shown below:
GET /api/v1/products/search?q=iPhone&category_id=1,2&price_min=1000000When using POST, search parameters are not visible in the URL but are sent in the Request body (as JSON or form-data if searching by images, or any other format suitable for the API functionality):
POST /api/v1/products/search
Content-Type: application/json
{
"filters": {
"text_search": {
"query": "iPhone",
"fields": ["name", "description", "tags"]
},
"price_range": {
"min": 1000000,
"max": 30000000
},
"categories": {
"include": [1, 2, 3],
"exclude": [99]
},
"advanced": {
"has_promotion": true,
"in_stock": true,
"rating_above": 4.0
}
},
"sort": [
{ "field": "popularity", "order": "desc" },
{ "field": "price", "order": "asc" }
],
"pagination": {
"page": 1,
"limit": 20
}
}Let's analyze each case to determine which method suits your API. Note that a system doesn't have to use only one method for all search APIs, so you can choose the method for each API individually, as long as it fits the API's functionality.
| GET | POST | |
|---|---|---|
| When to usage | ||
| - Few and simple search criteria (under 15 items) - URL length not exceeding 2048 characters - No sensitive data in search criteria - Want users to bookmark or share search links |
- Many or complex search criteria (nested objects) - URL length exceeding 2048 characters if using GET - Sensitive data in search criteria - Advanced filters with complex logic |
|
| Advantages | ||
| - Follows API design principles (GET = retrieve data) - Can be cached by browsers and CDN (Content Delivery Network) - Users can bookmark and share search result pages - Easy to test and debug via browser |
- No limit on search criteria length - Can send complex JSON structures - Sensitive data hidden in Request body - Flexible for advanced search features |
|
| Disadvantages | ||
| - URL length limitations - Search parameters visible in URL, not secure for sensitive data - Difficult to handle complex search criteria |
- Cannot be cached by browsers and CDN - Users cannot bookmark and share search result pages - Difficult to test and debug via browser - Doesn't follow design principles (search is "reading data" so should use GET) |
|
Methods NOT to use:
- PUT/PATCH: Doesn't follow design principles - search is reading data, not updating
- DELETE: Definitely not - search doesn't delete anything
When in doubt, start with GET for simplicity. You can add POST endpoints for advanced cases when necessary!
3. URI
Designing URIs for search functionality is like naming houses in a neighborhood. If named properly, anyone can find exactly where they want to go without needing to ask for directions. Some principles to keep in mind when designing URIs:
- Clearly indicate the object being searched
✅ Good Examples: GET /products, GET /products/search, GET /products/list
❌ Bad Example: GET /search - Consistent throughout the system
✅ Good Examples: GET /products/search, GET /orders/search, GET /customers/search
❌ Bad Examples: GET /products/search, GET /search-order, GET /customer/lookup - Use plural nouns
✅ Good Example: GET /products/search
❌ Bad Example: GET /product/search - Avoid too many nested levels
✅ Good Example: GET /categories/1/products
❌ Bad Example: GET /regions/1/cities/2/districts/3/stores/4/products - Avoid verbs in resource part
✅ Good Example: GET /products/search
❌ Bad Example: GET /search-products - As concise as possible, avoid unnecessary words or special characters
✅ Good Example: GET /users/{id}/products
❌ Bad Example: GET /products/search_by_user_id/{id}
4. Authentication
Before diving into the main content, there's one thing that cannot be overlooked - authentication.
If an API requires user authentication, this is the first step the API needs to perform. It's like a security guard at a building entrance asking: "Who are you and do you have an access card?"
If an API requires authentication but you cannot provide appropriate authentication information, the API will return HTTP Status Code 401 Unauthorized error.
◆ Questions to answer when designing authentication
- Does this API require authentication to be used? (i.e., only limited users are allowed to use it)
- If yes, how will users prove their identity? (individual users can authenticate via JWT Token or OAuth, while B2B users use API keys)
- Does this authentication information have an expiration time? (to prevent security attacks)
◆ Common authentication methods
Bearer Token (JWT Token)
- Like a time-limited VIP card containing securely encrypted user information.
- How it works:
- User logs in → Server creates JWT Token (like printing VIP card)
- Each API call → Client sends this token in Header
- Server checks token → Determines identity and permissions - Usage Example:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjM... - Characteristics:
- High security, can set expiration time
- Contains user information directly in token (no database query needed)
- Suitable for web/mobile applications
API Key
- Like a special password for applications, commonly used when system A wants to call system B's API.
- How it works:
- Admin creates API Key for each application/partner
- Application sends this key in every request
- Server checks key → Determines which application is calling - Usage Example:
X-API-Key: sk_live_51H7qYz2eZvKYlo2C... - Characteristics:
- Simple, easy to implement
- Suitable for B2B integration
- Can create multiple keys for each environment (dev/staging/prod)
Third-party Authentication (OAuth)
- Login via Google/Facebook without creating new accounts. Like using an ID card for procedures instead of creating new documents.
- How it works:
- User clicks "Login with Google"
- Redirect to Google → User logs into Google and grants permission to use information for the app
- Google returns information → App creates session for user - Usage Example:
Authorization: Bearer google_access_token_xyz - Characteristics:
- Users don't need to create accounts and remember additional passwords → Good user experience
- High security (guaranteed by Google/Facebook)
5. Authorization
After successful authentication, the system needs to decide what the user has access to. Authorization works like a card system in a building: having a card to enter the main door doesn't mean you can enter every room.
◆Two levels of authorization to design
1. Function-level Authorization
Decides who is allowed to call which APIs, for example:
- Admin: Access user management APIs, system reports
- Regular User: Only access APIs related to personal account
2. Data-level Authorization
Decides what data each role sees within the same API:
| Role | Product Search API | Order View API |
|---|---|---|
| Admin | All products (including discontinued, drafts) | Orders of all users |
| Regular User | Only products for sale | Only their own orders |
◆Common authorization errors to avoid
One of the most serious security vulnerabilities in API design related to authorization is Insecure Direct Object Reference (IDOR). This error occurs when APIs completely trust information from clients without checking ownership. Attackers can easily change IDs in URLs to access other people's data. For example, if user A has order ID 12345, they could try accessing order IDs 12346, 12347... to view other users' orders.
6. Input Parameters
Input parameters are like "filters" and "controls" that users can use to customize search results according to their preferences.
There are two common types of input parameters in search APIs: URI Parameters and Query Parameters.
- URI Parameters: Used to identify parent resources or fixed scopes. When an API declares URI Parameters, you must provide values for them when calling the API to identify the context.
- Query Parameters: Pass filtering conditions, search criteria, and display controls. Query Parameters are usually optional, meaning even if the code allows filtering by certain conditions, you don't have to send anything in Query Parameters.
- However, when using POST method, instead of using Query Parameters, we use Request body to send parameters. In terms of meaning, they are similar, only differing in the sending method, so I'll collectively refer to them as Query Parameters below.
Example: API to get product price history with URI /products/{product_id}/price_histories?dateFrom=2025-01-01&dateTo=2025-01-31
{product_id}is URI Parameters. Without product id, this API cannot be called because we don't know which product's price history to get.?dateFromand?dateToare Query Parameters. Without specifying time range, it will get the entire price history of this product.
◆Common types of input parameters in search APIs
Full-text / Keyword Search Parameters:
- Used to search by keywords across multiple fields. This is usually the central parameter of search APIs.- Parameter name is commonly
qorquery. - Search scope usually covers multiple columns: for example
name,description,sku,tags, sometimes even normalized content (normalized_text). This differs from attribute filtering parameters which target one to a few specific fields. - Match modes commonly used with this parameter type:
- exact: exact phrase match
- partial / contains: substring match (LIKE %...%)
- prefix: prefix match (autocomplete)
- fuzzy: accept spelling errors (configurable levenshtein distance)
- wildcard: support wildcard characters (*, ?), if enabled must limit length to prevent abuse
- boolean / advanced: support AND / OR / NOT operators, parentheses grouping ()
Filtering Parameters:
- Parameters that help narrow down result sets based on specific values of individual data fields.
- They allow users to filter precisely by specific criteria like price, category, status... instead of fuzzy searching.
- Common filter types used with this parameter type:
- Equality Filter: Find records where field value exactly matches specified value. Example:brand=apple(only get Apple brand products)
- Multi-value Filter: Find records where field value is in a list of specified values. Example:category_id=1,2,3(get products in categories 1, 2, or 3)
- Range Filter: Find records where field value is within a range from min to max. Example:price_min=100&price_max=500(get products priced from 100 to 500)
- Boolean Filter: Find records where boolean field equals true or false. Example:in_stock=true(only get products in stock)
- Multi-status Filter: Find records whose status is in a list of specified statuses. Example:status=active,draft(get products that are active for sale or still in draft)
Pagination Parameters:
- Pagination is a mechanism to divide search results into smaller pages, each containing a limited number of records.
- The purpose is to avoid loading too much data at once, improving performance and user experience.
- Parameters used for pagination include:
-page: Page number to retrieve (starting from 1). Example:page=2(get page 2)
-page_size/limit: Number of records returned per page. Example:page_size=20(20 records per page). If not provided, default value will be used (e.g.,20)
- Need to limit maximum value forpage_size/limit(e.g., 100) to prevent abuse (e.g., when clients intentionally requestlimit=10000causing DB to scan and serialize many records, increasing CPU, I/O, GC, or performing denial of service attacks...).
Sorting Parameters:
- Sorting is a way to control the display order of records in search results.
- The purpose is to help users view results in desired order (price ascending, newest, most popular...).
- Parameter name is commonly
sort. - Input value is usually in
field:directionformat. Example:sort=price:asc,name:desc(sort by price ascending, then by name descending) - Need a whitelist and only allow sorting by fields in this list. The purpose is to prevent arbitrary ORDER BY injection helping reduce SQL injection risk or ORM errors, avoid sorting by columns without indexes causing slowness and resource waste...
Response Control Parameters:
- Parameters that allow specifying exactly what data should be returned, helping optimize bandwidth and performance.
- Common parameters for controlling returned data:
- Select needed fields (fields): Specify which data fields to return. Example:fields=id,name,price(only return ID, name and price)
- Exclude unnecessary fields (exclude_fields): Specify fields not to return. Example:exclude_fields=description,internal_notes(don't return description, internal notes)
- Include facet statistics (include_facets): Request API to return additional statistics about record counts by group. Example:include_facets=true(return product counts by brand, category...)
- Highlight keywords (highlight): Request API to highlight search keywords in results. Example:q=iPhone&highlight=true(the word "iPhone" will be highlighted in product names)
User Context Parameters:
- Parameters that provide information about user context so APIs can return appropriate results.
- Common user context parameters:
-locale: User's language and region, to determine date format, language for product descriptions, error messages... Example:locale=vi-VN(Vietnamese - Vietnam)
-currency: Currency type for price display. Example:currency=VND(Vietnamese Dong)
◆Parameter naming principles
1. Parameter names must be clear, for example price_min instead of p_min.
2. Consistent naming convention, for example using snake_case or camelCase must be consistent.
3. Related parameters should have similar prefixes, for example price_min and price_max
7. Data Validation
Validation is the final "fortress gate" protecting the system from bad data and attacks. An API without proper validation is like leaving your house door wide open - anyone can enter and do whatever they want!
However, search APIs have a unique characteristic: "Wrong input means no results found, but doesn't break anything". For instance, product prices are only positive integers, but if the client sends priceFrom=-100&priceTo=-50, what will the API return? No products at all. This doesn't affect the system or data at all, so adding validation for priceFrom and priceTo to be positive integers is completely unnecessary.
Nevertheless, there are still cases where sending invalid values can affect the system, such as sending `limit=999999999` causing the database to load 999 million records, making the server run out of RAM and crash the entire system.
In this article, I will only mention the types of validation that are must-have and should-have in search APIs. Having stricter validation won't cause problems except making you spend more effort and making system maintenance and expansion more difficult.
◆Must-have validations:
- Maximum value limit for
limitparameter (e.g., 100). Requesting too many records on one page can cause server to run out of RAM and crash the system. - Block SQL Injection: Immediately reject if dangerous characters are detected like single quote (
'), double quote ("), semicolon (;), SQL comment (`--`) or SQL keywords likeSELECT,INSERT,DROP,UNION... - Block XSS Attack: Don't allow dangerous script tags like
<script>,javascript:,onload,onerror... in any parameters, especially search keywords. - Blacklist attack patterns: Block all known patterns that could be used to attack the system.
◆Should-have validations:
- Maximum value limit for
pageparameter (e.g., 10,000) to avoid deep pagination. Retrieving data from pages too far will slow down the database. - Maximum character limit for full-text search parameters. Keywords that are too long will slow down full-text search engines and waste processing resources.
- Whitelist for
sortparameter. The purpose is to prevent arbitrary ORDER BY injection helping reduce SQL injection risk or ORM errors, avoid sorting by columns without indexes causing slowness and resource waste. - Data type validation for parameters whose search objects have time types (date, datetime, timestamp...). If wrong data type or invalid value, should ignore these parameters, because adding them to queries can cause errors or return unexpected values.
Example: If client sends `created_from=2014-02-30` (non-existent date) or `created_from=28-02-24` (format not supported by system), should ignore these parameters instead of trying to process them and getting unexpected results or system errors.
Principles when writing validation design:
- Put at the beginning of the design so readers understand that data must be validated before proceeding with other processing.
- Mandatory validations that must exist in all search APIs should be put in common design, avoiding repetition in individual API designs.
- Error message content must be specific, clearly indicating which item has errors and what the error is. Best to create a common message list in the common section of the design.
Examples:{item_name} is required.{item_name} must be a positive integer.
8. Output Parameters
Principles when writing output parameter design
- Before writing the design, need to clearly analyze what information the screen/function will use, and only return truly needed information. For example, product list screen displayed to users only needs: ID, name, price, image, status; doesn't need detailed description, price history, internal notes.
- When requiring JOINs with other tables to get data, or complex return logic, must describe the JOIN conditions and this logic in detail in the design, avoiding different interpretations by different people.
- Always return pagination information (total records matching search criteria, maximum records per page, total pages, current page) in the design so frontend can implement pagination functionality.
- Should have default sorting, avoid returning random data without any order.
- Don't return sensitive information like passwords, tokens, internal information.
- Using snake_case or camelCase must be consistent with input parameters.
- Data types must be consistent across APIs. For example: empty arrays instead of null, time in ISO 8601 UTC standard, enums using machine-readable values.
- Structure should follow "envelope > data > items" model for system-wide synchronization and easy future expansion.
- Detailed descriptions for common functions like pagination should be separated and written in common design, instead of repeating in each API design.
- Only return additional information like facets (classification statistics), highlights (keyword highlighting), diagnostics (debug information) when the project really needs it.
Suggested response structure
{
"request_id": "a1c7f92d-5f1b-4f09-9e3d-1e2f7e8b2c11",
"success": true,
"timestamp": "2025-01-15T10:05:23.412Z",
"data": {
"items": [ ... ],
"pagination": { ... },
"sort": { ... }
}
}Where:
request_idreturned as UUID, used for server log tracing.successreturned as true/false. Although HTTP status code exists, this flag helps client handle uniformly.timestampreturned in ISO 8601 UTC standard, is the time server created the response, used for server log tracing.datais object wrapping main content. If error, can be omitted and replaced with error object.data.itemsis array containing result list. If no data matches search criteria, return empty array.data.paginationis metadata containing pagination information.data.sortis sorting information, helping client display applied sorting status.
Sample Search API Detailed Design
You can view and download the sample file (API Blueprint) to use as a standard framework for writing detailed search API design [here](https://github.com/sa-violetdang/advanced-api-documentation).
Conclusion
A good detailed design for search APIs must:
1. Have clear, consistent, easy-to-communicate names.
2. Choose GET or POST based on complexity & shareability.
3. URI concise, correctly expressing resource.
4. Always separate authentication and authorization (function-level & data-level).
5. Group input parameters by role (search / filter / sort / pagination / response control / context).
6. Sufficient validation to protect system, avoid over-engineering.
7. Minimal output, structured, extensible (envelope).
8. Reuse common parts to reduce duplication and increase maintainability.
Start with the standard framework, then gradually add business-specific features. Once the team agrees on format, development speed & communication quality will improve significantly. Good luck writing effective specs!