Implement global and per ip rate limiting * all tests passing * test fix * Enhances test environment for rate limiting Updates the test environment to better support rate limiting tests. - Increases rate limits in test container to avoid interference with legitimate test traffic. - Increases the polling interval for client status checks to reduce load on the rate limiter. - Adds logic to retry status checks if rate limiting is encountered. * Merge branch 'main' of bitbucket.org:aarete/query-orchestration into feature/rate-limiting * build fix * test fix
16 KiB
API Documentation
API Overview
The DoczyAI Query Orchestration Platform exposes a comprehensive REST API following OpenAPI 3.0.3 specification. The API is automatically generated from the specification file serviceAPIs/queryAPI.yaml using oapi-codegen.
Base Configuration
- Base URL:
https://doczy.com/v1(production),http://localhost:8080(development) - API Version: 0.0.1
- Content Type:
application/json - Documentation: Auto-generated Swagger UI at
/swagger/index.html
Authentication and Authorization
Security Schemes
JWT Authentication
- Type: Bearer token authentication
- Header:
Authorization: Bearer <token> - Token Source: JWT cookie set via OAuth flow
OAuth2 Flow
- Type: OAuth2 Authorization Code Flow
- Provider: AWS Cognito
- Scopes: Group-based permissions
Authentication Endpoints
GET /login
Initiates OAuth2 authentication flow with AWS Cognito.
Response: Redirect to Cognito login page
GET /login-callback
OAuth2 callback handler that processes authentication response.
Parameters:
code(query): Authorization code from Cognitostate(query): CSRF protection parameter
Response: Sets JWT cookie and redirects to dashboard
GET /logout
Terminates user session and clears authentication cookies.
Response: Redirect to login page
GET /home
Protected dashboard endpoint showing user information.
Rate Limiting
The queryAPI service implements dual-layer rate limiting to protect against DDoS attacks and ensure fair resource allocation.
Rate Limit Architecture
Dual-Layer Protection:
- Layer 1: Global/Aggregate Rate Limiting - Protects total system capacity across all IP addresses
- Layer 2: Per-IP Rate Limiting - Prevents individual client abuse
Both layers must pass for a request to proceed. The global limiter checks first for faster failure paths under DDoS conditions.
Default Rate Limits
By default, all endpoints are limited to:
- Global: 500 requests per second total (1000 burst)
- Per-IP: 5 requests per second per IP (10 burst)
- Cleanup Interval: 3 minutes for inactive IP limiters
Important: No endpoints are exempt from rate limiting. This is a security requirement to prevent DDoS attacks.
Configuration
Rate limits are configured via environment variables:
# Global/Aggregate rate limiting (protects total system capacity)
RATE_LIMIT_GLOBAL_RATE=500 # total req/s across ALL IPs
RATE_LIMIT_GLOBAL_BURST=1000 # total burst capacity across ALL IPs
# Per-IP rate limiting (protects against individual abusers)
RATE_LIMIT_DEFAULT_RATE=5 # req/s per IP
RATE_LIMIT_DEFAULT_BURST=10 # burst per IP
RATE_LIMIT_EXPIRES_IN=180 # seconds (3 minutes)
# Per-endpoint overrides (JSON format)
RATE_LIMIT_ENDPOINT_OVERRIDES={}
Configuring Endpoint-Specific Overrides
Endpoints can have custom rate limits based on their resource requirements. Configure using the RATE_LIMIT_ENDPOINT_OVERRIDES environment variable.
Endpoint Route Format
Routes use the format: {HTTP_METHOD} {PATH}
Examples:
GET /healthPOST /documentsGET /documents/{id}(use route template, not actual IDs)POST /client/{id}/document/batch
Override Configuration Structure
Each endpoint requires 4 values:
{
"ENDPOINT_ROUTE": {
"global_rate": 100, // Total req/s across ALL IPs
"global_burst": 200, // Total burst across ALL IPs
"rate": 5, // Req/s per individual IP
"burst": 10 // Burst per individual IP
}
}
Setting Environment Variable
Single endpoint:
export RATE_LIMIT_ENDPOINT_OVERRIDES='{"POST /documents": {"global_rate": 50, "global_burst": 100, "rate": 5, "burst": 10}}'
Multiple endpoints:
export RATE_LIMIT_ENDPOINT_OVERRIDES='{
"GET /health": {"global_rate": 500, "global_burst": 1000, "rate": 100, "burst": 200},
"GET /metrics": {"global_rate": 100, "global_burst": 200, "rate": 10, "burst": 20},
"GET /swagger/*": {"global_rate": 200, "global_burst": 400, "rate": 50, "burst": 100},
"GET /auth/login": {"global_rate": 50, "global_burst": 100, "rate": 5, "burst": 10},
"GET /auth/callback": {"global_rate": 100, "global_burst": 200, "rate": 10, "burst": 20},
"GET /auth/logout": {"global_rate": 100, "global_burst": 200, "rate": 10, "burst": 20},
"GET /auth/home": {"global_rate": 100, "global_burst": 200, "rate": 10, "burst": 20},
"POST /documents": {"global_rate": 50, "global_burst": 100, "rate": 5, "burst": 10},
"POST /documents/batch": {"global_rate": 10, "global_burst": 20, "rate": 1, "burst": 2},
"GET /documents/{id}": {"global_rate": 200, "global_burst": 400, "rate": 20, "burst": 40},
"GET /client/{id}": {"global_rate": 200, "global_burst": 400, "rate": 20, "burst": 40},
"POST /query/test": {"global_rate": 20, "global_burst": 40, "rate": 2, "burst": 5},
"POST /export": {"global_rate": 10, "global_burst": 20, "rate": 1, "burst": 2}
}'
In .env file (single line, valid JSON):
RATE_LIMIT_ENDPOINT_OVERRIDES={"GET /health":{"global_rate":500,"global_burst":1000,"rate":100,"burst":200},"POST /documents":{"global_rate":50,"global_burst":100,"rate":5,"burst":10}}
Recommended Endpoint Tiers
Tier 1: Monitoring/Infrastructure (High Limits)
{
"GET /health": {"global_rate": 500, "global_burst": 1000, "rate": 100, "burst": 200},
"GET /metrics": {"global_rate": 100, "global_burst": 200, "rate": 10, "burst": 20},
"GET /swagger/*": {"global_rate": 200, "global_burst": 400, "rate": 50, "burst": 100}
}
Tier 2: Authentication (Moderate - Prevent Brute Force)
{
"GET /auth/login": {"global_rate": 50, "global_burst": 100, "rate": 5, "burst": 10},
"GET /auth/callback": {"global_rate": 100, "global_burst": 200, "rate": 10, "burst": 20},
"GET /auth/logout": {"global_rate": 100, "global_burst": 200, "rate": 10, "burst": 20}
}
Tier 3: Read Operations (Moderate)
{
"GET /client/{id}": {"global_rate": 200, "global_burst": 400, "rate": 20, "burst": 40},
"GET /documents/{id}": {"global_rate": 200, "global_burst": 400, "rate": 20, "burst": 40},
"GET /collector/{id}": {"global_rate": 200, "global_burst": 400, "rate": 20, "burst": 40}
}
Tier 4: Write/Processing (Low - Resource Intensive)
{
"POST /documents": {"global_rate": 50, "global_burst": 100, "rate": 5, "burst": 10},
"POST /documents/batch": {"global_rate": 10, "global_burst": 20, "rate": 1, "burst": 2},
"POST /query/test": {"global_rate": 20, "global_burst": 40, "rate": 2, "burst": 5},
"POST /export": {"global_rate": 10, "global_burst": 20, "rate": 1, "burst": 2}
}
Verification
After restarting the service, check logs for confirmation:
level=INFO msg="Rate limiting enabled" global_rate=1000 global_burst=2000
default_rate=10 default_burst=20 overrides_count=12
The overrides_count should match your configured endpoints.
Troubleshooting
Invalid JSON: Validate with echo $RATE_LIMIT_ENDPOINT_OVERRIDES | jq .
Override not applied:
- Verify HTTP method matches exactly (e.g.,
POSTnotpost) - Use route template for paths with parameters (
/documents/{id}not/documents/123) - Check for extra/missing slashes in path
- Enable debug logging:
DEBUG=trueto see route matching
Rate Limit Headers
All API responses include a RateLimit header indicating the current limit:
RateLimit: 20;window=2
Format: {burst};window={seconds}
burst: Maximum requests allowed in a burstwindow: Time window in seconds (calculated as burst/rate)
Rate Limit Exceeded Response
When the rate limit is exceeded, the API returns:
HTTP Status: 429 Too Many Requests
Headers:
Retry-After: {seconds}- Number of seconds before retryingRateLimit: {burst};window={seconds}- Current rate limit
Response Body:
{
"message": "rate limit exceeded"
}
Testing Rate Limits
Test rate limiting behavior with curl:
# Exceed rate limit by making rapid requests
for i in {1..25}; do
curl -i http://localhost:8080/client/AAA \
-H "Authorization: Bearer $TOKEN"
done
# Observe 429 responses after burst is exhausted
Technical Implementation
- Algorithm: Dual-layer token bucket (golang.org/x/time/rate)
- Layer 1: Global rate limiter (shared across all IPs)
- Layer 2: Per-IP rate limiter (separate bucket per IP via c.RealIP())
- Storage: In-memory with automatic cleanup
- Middleware Position: Applied BEFORE authentication to protect auth endpoints
- Protection: Defends against both distributed DDoS and single-source attacks
Security: Requires authentication Response: HTML dashboard page
Authorization Model
Permission Groups
- exporters: Can access export operations
- uploaders: Can upload documents and manage clients
- querybuilders: Can create and modify queries
Route Permissions
/client/* → exporters, uploaders, querybuilders
/document/* → exporters, querybuilders
/query/* → exporters, uploaders, querybuilders
/export/* → exporters only
/health → public (no authentication required)
Core API Endpoints
Client Management
POST /client
Creates a new client in the system.
Security: Requires uploaders or querybuilders permission
Request Body:
{
"clientId": "string (max 256 chars)",
"name": "string (max 256 chars)"
}
Responses:
201: Client created successfully400: Invalid request data401: Authentication required403: Insufficient permissions
GET /client/{id}
Retrieves client details by ID.
Path Parameters:
id: Client identifier (string, max 256 chars)
Response Example:
{
"clientId": "acme-corp",
"name": "ACME Corporation"
}
PATCH /client/{id}
Updates client information.
Request Body:
{
"name": "string (max 256 chars)"
}
GET /client/{id}/status
Returns client synchronization status.
Response Example:
{
"status": "IN_SYNC" | "NOT_SYNCED" | "NOT_SYNCING"
}
Document Operations
POST /client/{id}/document
Uploads a document for a specific client.
Content Type: multipart/form-data
Max File Size: 100GB
Supported Types: PDF (extensible)
Form Parameters:
file: Document file (required)
Response:
{
"documentId": "uuid",
"clientId": "string",
"filename": "string",
"uploadedAt": "2024-01-01T00:00:00Z"
}
GET /client/{id}/document
Lists documents for a client with optional filtering.
Query Parameters:
limit: Maximum results (integer)offset: Pagination offset (integer)
Response:
{
"documents": [
{
"documentId": "uuid",
"clientId": "string",
"filename": "string",
"status": "string",
"uploadedAt": "2024-01-01T00:00:00Z"
}
],
"total": "integer"
}
GET /document/{id}
Retrieves document metadata by ID.
Path Parameters:
id: Document UUID
Batch Document Operations
POST /client/{id}/document/batch
Uploads a ZIP archive containing multiple PDF documents for asynchronous batch processing.
Security: Requires uploaders or querybuilders permission
Content Type: multipart/form-data
Max Archive Size: 1GB
Supported Archive Type: ZIP only
Form Parameters:
archive: ZIP file containing PDF documents (required)
Response (202 Accepted):
{
"batch_id": "uuid",
"status": "processing",
"status_url": "/client/AAA/document/batch/019580df-ef65-7676-8de9-94435a93337a"
}
GET /client/{id}/document/batch
Lists batch uploads for a client with pagination support.
Query Parameters:
limit: Maximum results (integer, default: 20)offset: Pagination offset (integer, default: 0)
Response:
{
"batches": [
{
"batch_id": "uuid",
"status": "completed" | "processing" | "failed" | "cancelled",
"total_documents": 15,
"processed_documents": 15,
"failed_documents": 0,
"created_at": "2024-01-01T00:00:00Z",
"completed_at": "2024-01-01T00:05:00Z"
}
],
"total": 42
}
GET /client/{id}/document/batch/{batch_id}
Retrieves detailed status information for a specific batch upload.
Path Parameters:
batch_id: Batch upload identifier (UUID)
Response:
{
"batch_id": "uuid",
"status": "completed",
"total_documents": 15,
"processed_documents": 15,
"failed_documents": 0,
"failed_filenames": [],
"created_at": "2024-01-01T00:00:00Z",
"completed_at": "2024-01-01T00:05:00Z"
}
DELETE /client/{id}/document/batch/{batch_id}
Cancels a batch upload that is currently processing.
Path Parameters:
batch_id: Batch upload identifier (UUID)
Response:
204: Batch upload cancelled successfully409: Cannot cancel completed or already cancelled batch
Query Management
GET /query
Lists all available queries in the system.
Response:
{
"queries": [
{
"queryId": "uuid",
"queryType": "JSON_EXTRACTOR" | "CONTEXT_FULL",
"name": "string",
"description": "string",
"version": "integer"
}
]
}
POST /query
Creates a new query definition.
Request Body:
{
"queryType": "JSON_EXTRACTOR" | "CONTEXT_FULL",
"name": "string (max 256 chars)",
"description": "string (max 512 chars)",
"config": "object (JSON configuration)"
}
GET /query/{id}
Retrieves query details including configuration.
PATCH /query/{id}
Updates query definition and increments version.
POST /query/{id}/test
Tests query execution against sample data.
Request Body:
{
"testData": "string",
"documentId": "uuid (optional)"
}
Collector Configuration
GET /client/{id}/collector
Retrieves collector configuration for a client.
Response:
{
"clientId": "string",
"queries": [
{
"name": "string",
"queryId": "uuid"
}
],
"minCleanVersion": "integer",
"minTextVersion": "integer"
}
PATCH /client/{id}/collector
Updates collector configuration.
Export Operations
POST /client/{id}/export
Triggers data export for a client.
Request Body:
{
"format": "CSV" | "JSON" | "EXCEL",
"filters": {
"queryIds": ["uuid"],
"dateRange": {
"start": "2024-01-01T00:00:00Z",
"end": "2024-12-31T23:59:59Z"
}
}
}
GET /export/{id}
Checks export status and retrieves download URL.
Response:
{
"exportId": "uuid",
"status": "PENDING" | "PROCESSING" | "COMPLETED" | "FAILED",
"downloadUrl": "string (when completed)",
"createdAt": "2024-01-01T00:00:00Z",
"completedAt": "2024-01-01T00:05:00Z"
}
Request/Response Schemas
Standard Response Headers
All API responses include:
RateLimit: Current rate limit status (format:100;window=60)Content-Type:application/json(except file downloads)
Error Response Format
{
"message": "Error description (max 256 chars)"
}
Common HTTP Status Codes
200: Success201: Created400: Bad Request (validation errors)401: Unauthorized (authentication required)403: Forbidden (insufficient permissions)404: Not Found429: Too Many Requests (rate limited)500: Internal Server Error
Rate Limiting
Current Limits
- Rate: 100 requests per 60-second window
- Scope: Per authenticated user
- Headers:
RateLimitheader shows current status - Exceeded: Returns
429withRetry-Afterheader
API Versioning
Current Strategy
- URL-based versioning:
/v1prefix - Backward Compatibility: Maintained within major versions
- OpenAPI Version: 0.0.1 (development phase)
Future Considerations
- Semantic versioning for production releases
- Deprecation notices for breaking changes
- Version sunset policies
Code Generation
OpenAPI Integration
The API is generated from the OpenAPI specification using:
oapi-codegen -generate "types,server" -o api.gen.go serviceAPIs/queryAPI.yaml
Generated Artifacts
- Type-safe Go structs for requests/responses
- Echo framework route handlers
- OpenAPI validation middleware
- Swagger documentation
Validation
- Automatic request validation against OpenAPI schema
- Response validation in development mode
- Parameter binding with type safety