Response Format
flAPI provides flexible response formatting with JSON by default, plus support for CSV and Parquet. This guide covers response structure, metadata, error handling, and format customization.
Default JSON Response
Basic Structure
{
"data": [
{"id": 1, "name": "Product A", "price": 29.99},
{"id": 2, "name": "Product B", "price": 39.99}
],
"metadata": {
"count": 2,
"execution_time_ms": 1.5,
"cached": true
}
}
Response Fields
Field | Type | Description |
---|---|---|
data | Array | Query results |
metadata | Object | Execution information |
metadata.count | Integer | Number of rows returned |
metadata.execution_time_ms | Float | Query execution time |
metadata.cached | Boolean | Whether result came from cache |
Response Configuration
Configure response format in your endpoint:
url-path: /products/
response:
format: json # json, csv, or parquet
include_metadata: true
pagination:
enabled: true
default_limit: 20
max_limit: 100
Response Formats
JSON (Default)
Content-Type: application/json
response:
format: json
Example:
$ curl http://localhost:8080/products/
{
"data": [
{"id": 1, "name": "Product A"}
]
}
CSV
Content-Type: text/csv
response:
format: csv
Example:
$ curl http://localhost:8080/products/?format=csv
id,name,price
1,Product A,29.99
2,Product B,39.99
Headers:
Content-Type: text/csv
Content-Disposition: attachment; filename=products.csv
Parquet
Content-Type: application/octet-stream
response:
format: parquet
Example:
$ curl http://localhost:8080/products/?format=parquet -o products.parquet
Perfect for:
- Data exports
- Analytics tools
- Data pipelines
- Large datasets
Pagination
Enable Pagination
response:
pagination:
enabled: true
default_limit: 20
max_limit: 100
Paginated Response
{
"data": [...],
"pagination": {
"limit": 20,
"offset": 0,
"total": 150,
"has_more": true
},
"links": {
"self": "/products?limit=20&offset=0",
"next": "/products?limit=20&offset=20",
"prev": null
}
}
Pagination Parameters
Add to your endpoint:
request:
- field-name: limit
field-in: query
required: false
validators:
- type: int
min: 1
max: 100
- field-name: offset
field-in: query
required: false
validators:
- type: int
min: 0
SQL template:
SELECT * FROM products
LIMIT {{{params.limit|20}}}
OFFSET {{{params.offset|0}}}
Navigation Example
# Page 1
curl http://localhost:8080/products?limit=20&offset=0
# Page 2
curl http://localhost:8080/products?limit=20&offset=20
# Page 3
curl http://localhost:8080/products?limit=20&offset=40
Metadata
Standard Metadata
Included by default:
{
"metadata": {
"count": 25,
"execution_time_ms": 2.3,
"cached": true,
"cache_age_seconds": 120
}
}
Custom Metadata
Add custom metadata in SQL:
SELECT
*,
CURRENT_TIMESTAMP as query_timestamp,
VERSION() as database_version
FROM products
Disable Metadata
response:
include_metadata: false
Returns just the data:
[
{"id": 1, "name": "Product A"},
{"id": 2, "name": "Product B"}
]
Error Responses
Standard Error Format
{
"error": {
"message": "Parameter 'category' must be one of: electronics, clothing, books",
"code": "VALIDATION_ERROR",
"status": 400,
"details": {
"parameter": "category",
"provided": "invalid",
"expected": ["electronics", "clothing", "books"]
}
}
}
HTTP Status Codes
Code | Meaning | Example |
---|---|---|
200 | Success | Query executed successfully |
400 | Bad Request | Invalid parameter value |
401 | Unauthorized | Missing or invalid authentication |
403 | Forbidden | User lacks required permissions |
404 | Not Found | Endpoint doesn't exist |
429 | Too Many Requests | Rate limit exceeded |
500 | Internal Server Error | Query execution failed |
Error Types
Validation Error (400):
{
"error": {
"message": "Missing required parameter: customer_id",
"code": "VALIDATION_ERROR",
"status": 400
}
}
Authentication Error (401):
{
"error": {
"message": "Invalid or missing authentication token",
"code": "AUTHENTICATION_ERROR",
"status": 401
}
}
Query Error (500):
{
"error": {
"message": "Query execution failed",
"code": "QUERY_ERROR",
"status": 500,
"details": {
"sql_state": "42P01",
"message": "relation 'missing_table' does not exist"
}
}
}
Content Negotiation
Accept Header
Request specific format using Accept header:
# JSON (default)
curl -H "Accept: application/json" http://localhost:8080/products/
# CSV
curl -H "Accept: text/csv" http://localhost:8080/products/
# Parquet
curl -H "Accept: application/parquet" http://localhost:8080/products/
Query Parameter
Override format with query parameter:
# Force CSV
curl http://localhost:8080/products/?format=csv
# Force Parquet
curl http://localhost:8080/products/?format=parquet
Column Formatting
Date/Time Formatting
DuckDB returns timestamps in ISO 8601:
{
"created_at": "2024-01-15T10:30:00Z",
"date": "2024-01-15"
}
Format in SQL if needed:
SELECT
STRFTIME(created_at, '%Y-%m-%d') as date,
STRFTIME(created_at, '%H:%M:%S') as time
FROM orders
Number Formatting
Precise decimals preserved:
{
"price": 29.99,
"quantity": 150,
"weight_kg": 2.456
}
Round in SQL if needed:
SELECT
ROUND(price, 2) as price,
ROUND(weight_kg, 3) as weight_kg
FROM products
NULL Handling
NULL values returned as null
:
{
"id": 123,
"optional_field": null
}
Convert in SQL if needed:
SELECT
COALESCE(optional_field, 'N/A') as optional_field
FROM table
Compression
Enable Compression
flAPI automatically compresses responses > 1KB:
response:
compression:
enabled: true
min_size_bytes: 1024
algorithms: ['gzip', 'deflate']
Response Headers
Content-Encoding: gzip
Vary: Accept-Encoding
Client Usage
Most HTTP clients handle decompression automatically:
# curl handles gzip automatically
curl http://localhost:8080/products/
# Disable compression
curl --compressed http://localhost:8080/products/
Streaming Responses
For large datasets, enable streaming:
response:
streaming:
enabled: true
chunk_size: 1000 # Rows per chunk
Benefits:
- Lower memory usage
- Faster time to first byte
- Better for large datasets
Client receives data incrementally:
{"id": 1, "name": "Product A"}
{"id": 2, "name": "Product B"}
{"id": 3, "name": "Product C"}
...
CORS Configuration
Enable CORS for web applications:
response:
cors:
enabled: true
origins: ['https://example.com', 'https://app.example.com']
methods: ['GET', 'POST', 'PUT', 'DELETE']
headers: ['Content-Type', 'Authorization']
credentials: true
Response Headers:
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type, Authorization
Caching Headers
flAPI sets appropriate cache headers:
Cache-Control: public, max-age=3600
ETag: "a1b2c3d4e5f6"
Last-Modified: Mon, 15 Jan 2024 10:30:00 GMT
Configure caching headers:
response:
cache_control:
max_age: 3600 # 1 hour
public: true
Best Practices
1. Always Include Metadata
# ✅ Good: Metadata helps debugging
response:
include_metadata: true
# ❌ Bad: No execution context
response:
include_metadata: false
2. Implement Pagination
# ✅ Good: Prevents large responses
response:
pagination:
enabled: true
max_limit: 100
# ❌ Bad: Unlimited results
response:
pagination:
enabled: false
3. Use Appropriate Formats
# For web apps: JSON
format: json
# For data exports: CSV or Parquet
format: csv
# For analytics: Parquet
format: parquet
4. Set Reasonable Limits
-- ✅ Good: Always limit results
SELECT * FROM large_table
LIMIT 1000
-- ❌ Bad: No limit
SELECT * FROM large_table
5. Return Clear Errors
# ✅ Good: Detailed error messages
error_message: "Invalid segment. Use AUTOMOTIVE, BUILDING, or FURNITURE"
# ❌ Bad: Vague errors
error_message: "Invalid input"
Testing Responses
Test JSON Response
$ curl http://localhost:8080/products/ | jq
{
"data": [...]
}
Test CSV Response
$ curl "http://localhost:8080/products/?format=csv"
id,name,price
1,Product A,29.99
Test Error Handling
$ curl "http://localhost:8080/products/?category=invalid"
{
"error": {
"message": "Parameter 'category' must be one of: ...",
"status": 400
}
}
Test Pagination
$ curl "http://localhost:8080/products/?limit=5" | jq '.pagination'
{
"limit": 5,
"offset": 0,
"total": 150,
"has_more": true
}
Next Steps
- Parameters: Configure request parameters
- Validation: Validate input
- Caching: Optimize response times
- Authentication: Secure your APIs