OutSystems does not include a built-in API gateway. For production deployments, place an external gateway (AWS API Gateway, Kong, Apigee, Azure APIM) in front of your exposed OutSystems REST APIs. For lightweight rate limiting in O11 without an external gateway, implement a counter entity in the OnAuthentication callback. ODC provides platform-level adaptive throttling at 100 req/min for management APIs, but application-level rate limiting still requires external infrastructure.
API Gateway Architecture for OutSystems Applications
When you expose REST APIs from OutSystems and open them to external consumers, you immediately need capabilities that OutSystems does not provide out of the box: rate limiting, request throttling, API key lifecycle management, usage analytics, developer portal documentation, and DDoS protection. The correct architecture is to put an industry-standard API gateway between the internet and your OutSystems module. This tutorial covers both a lightweight in-OutSystems rate limiting pattern for simpler cases and the recommended external gateway integration pattern for enterprise deployments.
Prerequisites
- An exposed REST API already working in OutSystems (Logic tab → Integrations → REST → Expose REST API)
- Authentication already configured on the exposed API (see outsystems-api-authentication tutorial)
- For external gateway sections: access to AWS API Gateway, Kong, Apigee, or Azure API Management
- Service Center access to monitor API usage logs (O11) or ODC Portal for traces (ODC)
Step-by-step guide
Understand what OutSystems provides vs what you need a gateway for
Understand what OutSystems provides vs what you need a gateway for
OutSystems exposed REST APIs provide by default: - CORS headers (Access-Control-Allow-Origin: *) - Basic and Custom Authentication via OnAuthentication callback - HTTPS (SSL/TLS) on OutSystems Cloud environments - Auto-generated OpenAPI/Swagger documentation - Request/response logging in Service Center → Monitoring → Integrations OutSystems does NOT provide: - Rate limiting or request throttling - Request/response transformation at the network edge - API key lifecycle management (issuance, rotation, quotas) - Developer portal with self-service API key sign-up - DDoS protection at the API layer - Geographic routing or latency-based routing - Monetization and billing per API call For internal integrations (other OutSystems modules, trusted internal services), built-in authentication is usually sufficient. For external APIs exposed to third-party developers, a gateway is required.
Expected result: You have a clear understanding of the capability gaps and can make an informed architectural decision about whether to use an external gateway.
Implement lightweight rate limiting in the OnAuthentication callback
Implement lightweight rate limiting in the OnAuthentication callback
For cases where an external gateway is not available, implement rate limiting directly in OutSystems using a database counter pattern. First, create an entity: Data tab → Entities → Database → right-click → Add Entity. Name it ApiRateLimit with attributes: - ApiKeyId (ApiKey Identifier) - WindowStart (DateTime) - RequestCount (Integer) - MaxRequests (Integer, default: 100) - WindowSeconds (Integer, default: 60) In the OnAuthentication action (Logic tab → Integrations → REST → [YourAPI] → OnAuthentication), after the API key is validated, add: Start → ... validate API key ... → [GetCurrentWindow: Aggregate, Filter: ApiRateLimit.ApiKeyId = ValidatedKeyId AND ApiRateLimit.WindowStart >= AddSeconds(CurrDateTime(), -ApiRateLimit.WindowSeconds), MaxRecords: 1] → If (GetCurrentWindow.List.Empty) [True] CreateApiRateLimit (WindowStart = CurrDateTime(), RequestCount = 1, MaxRequests = 100, WindowSeconds = 60, ApiKeyId = ValidatedKeyId) [False] If (GetCurrentWindow.List.Current.ApiRateLimit.RequestCount >= GetCurrentWindow.List.Current.ApiRateLimit.MaxRequests) [True] Raise Exception (RateLimitExceeded, User Exception) -- returns HTTP 429 [False] UpdateApiRateLimit (Id = GetCurrentWindow.Id, RequestCount = GetCurrentWindow.RequestCount + 1) → End
Expected result: API consumers exceeding their request quota receive HTTP 429 Too Many Requests. The rate limit resets after the configured window.
Add Retry-After header to 429 responses
Add Retry-After header to 429 responses
When returning a 429 rate limit error, best practice is to include a Retry-After header telling the consumer when they can retry. In the OnAuthentication callback, when raising RateLimitExceeded, you cannot directly add a response header from the exception. Instead, use the OnAfterResponse callback on the same REST API node: In OnAfterResponse (set On After Response → New OnAfterResponse): Start → If (Response.StatusCode = 429) → [True] GetRequestHeader (Name = "X-API-Key") → GetRateLimitWindow (Aggregate for the key) → Assign RetryAfterSeconds = AddSeconds(GetRateLimitWindow.List.Current.ApiRateLimit.WindowStart, GetRateLimitWindow.List.Current.ApiRateLimit.WindowSeconds) → Use SetResponseHeader (HTTPRequestHandler) to add Retry-After header → [False] pass → Assign CustomizedResponse = Response → End Alternatively, include the retry-after information in the exception message so it appears in the JSON error body: "Rate limit exceeded. Retry after " + IntegerToText(SecondsRemaining) + " seconds."
Expected result: Rate-limited responses include timing information so consumers implement proper back-off without guessing.
Integrate AWS API Gateway as a front-end proxy
Integrate AWS API Gateway as a front-end proxy
For production APIs, place AWS API Gateway in front of OutSystems: 1. In AWS Console, create a new REST API in API Gateway 2. Create a resource and method matching your OutSystems endpoint (e.g., GET /products/{id}) 3. Set Integration Type to HTTP Proxy 4. Set Endpoint URL to your OutSystems endpoint: https://yourserver.outsystemscloud.com/Module/rest/v1/products/{id} 5. Configure method request to pass the {id} path parameter through 6. Add a Usage Plan: API Gateway → Usage Plans → Create → set Throttle (rate/burst) and Quota (daily limit) 7. Create an API Key in API Gateway and attach it to the Usage Plan 8. Deploy the API to a Stage (dev/prod) OutSystems-side configuration: - Update your OnAuthentication to validate the AWS API Gateway consumer key OR trust that AWS handles authentication and configure OutSystems to accept only requests from the AWS Gateway IP ranges - Consider removing OutSystems-level authentication for gateway traffic to avoid double authentication overhead Environment variable configuration: Store the API Gateway URL as a Site Property for use in documentation and logging.
Expected result: External consumers access your OutSystems APIs through the AWS API Gateway URL. Rate limiting, API key management, and usage analytics are handled by AWS.
Configure Kong as a self-hosted gateway for on-premises O11
Configure Kong as a self-hosted gateway for on-premises O11
For on-premises O11 deployments, Kong is a popular open-source gateway: 1. Install Kong (Docker: docker run -d --name kong kong:latest) 2. Configure a Service pointing to your OutSystems server: POST http://localhost:8001/services { "name": "outsystems-api", "url": "https://yourserver/Module/rest/v1" } 3. Add a Route: POST http://localhost:8001/services/outsystems-api/routes { "paths": ["/api/v1"], "strip_path": true } 4. Enable the Rate Limiting plugin: POST http://localhost:8001/services/outsystems-api/plugins { "name": "rate-limiting", "config": { "minute": 100, "hour": 1000, "policy": "local" } } 5. Enable the Key Auth plugin for API key management: { "name": "key-auth", "config": { "key_names": ["X-API-Key"] } } With Kong handling authentication and rate limiting, update your OutSystems OnAuthentication to either pass through (Authentication = None — only safe if Kong and OutSystems are on the same private network) or validate an internal key that Kong injects after its own validation.
Expected result: Kong proxies all requests to OutSystems, enforcing rate limits and API key validation before they reach the OutSystems module.
Understand ODC platform-level rate limiting
Understand ODC platform-level rate limiting
ODC differs from O11 in rate limiting: Platform-level limits (ODC, automatic, cannot be modified): - Management APIs (ODC Portal APIs): 100 requests/minute with adaptive throttling - Application REST APIs (your exposed endpoints): No platform-level request throttling — rate limiting is your responsibility - Container scaling: ODC scales containers automatically, so rate limiting is about protecting downstream systems and controlling costs, not protecting OutSystems itself For ODC application APIs: - Use the same OnAuthentication counter pattern from step 2 (works identically in ODC) - Preferred: integrate with AWS API Gateway (OutSystems uses AWS, so low latency) or use Cloudflare Workers in front of the ODC app domain - ODC Portal → App Security → provides vulnerability scanning but not rate limiting ODC adaptive throttling for management APIs returns HTTP 429 with Retry-After header automatically when the 100 req/min limit is exceeded — no configuration needed.
Expected result: You understand ODC's rate limiting model and have implemented the appropriate strategy for your deployment.
Complete working example
1/* OnAuthentication Action — API Key Validation + Rate Limiting2 Attached to: Exposed REST API node3 Entities required:4 - ApiKey (KeyValue Text, IsActive Boolean, ExpiresOn DateTime)5 - ApiRateLimit (ApiKeyId FK, WindowStart DateTime, RequestCount Integer, MaxRequests Integer, WindowSecs Integer)6*/78Start9 |10 v11[GetRequestHeader] Name = "X-API-Key" -> ApiKeyValue12 |13 v14[GetApiKeyRecord] -- Aggregate15 Filter: ApiKey.KeyValue = ApiKeyValue16 AND ApiKey.IsActive = True17 MaxRecords: 118 |19 v20[If] GetApiKeyRecord.List.Empty OR ApiKey.ExpiresOn < CurrDateTime()21 |22 True -->23 [Raise Exception: InvalidApiKey (Security Exception)]24 -- HTTP 401 returned25 |26 False -->27 [Assign] ValidatedKeyId = GetApiKeyRecord.List.Current.ApiKey.Id28 [Assign] MaxReq = GetApiKeyRecord.List.Current.ApiKey.RateLimit -- attribute on ApiKey entity29 |30 v31 [GetCurrentWindow] -- Aggregate32 Filter: ApiRateLimit.ApiKeyId = ValidatedKeyId33 AND ApiRateLimit.WindowStart >= AddSeconds(CurrDateTime(), -60)34 MaxRecords: 135 |36 v37 [If] GetCurrentWindow.List.Empty38 |39 True -->40 [CreateApiRateLimit]41 ApiKeyId = ValidatedKeyId42 WindowStart = CurrDateTime()43 RequestCount = 144 MaxRequests = MaxReq45 WindowSecs = 6046 |47 False -->48 [If] GetCurrentWindow.List.Current.ApiRateLimit.RequestCount >= MaxReq49 |50 True -->51 [Raise Exception: RateLimitExceeded (User Exception)52 Message = "Rate limit exceeded. Retry after "53 + IntegerToText(60 - DiffSeconds(GetCurrentWindow.List.Current.ApiRateLimit.WindowStart, CurrDateTime()))54 + " seconds."]55 -- HTTP 429 returned56 |57 False -->58 [UpdateApiRateLimit]59 Id = GetCurrentWindow.List.Current.ApiRateLimit.Id60 RequestCount = GetCurrentWindow.List.Current.ApiRateLimit.RequestCount + 161 |62 v63EndCommon mistakes
Why it's a problem: Assuming OutSystems exposed REST APIs are protected from DDoS or abusive traffic by default
How to avoid: OutSystems Cloud provides basic network-level protection, but application-layer rate limiting and request throttling require explicit implementation. An unprotected public API with no rate limiting can be hammered by automated clients, exhausting your OutSystems platform capacity.
Why it's a problem: Implementing rate limiting in the action flow of each individual REST method instead of in OnAuthentication
How to avoid: Rate limiting should run before the method logic executes — implement it in OnAuthentication so it applies consistently to all methods in the API without duplicating logic. Per-method implementation is error-prone and easy to miss when adding new methods.
Why it's a problem: Not cleaning up old rate limit window records, causing the counter entity to grow indefinitely
How to avoid: Create a Timer (Logic tab → right-click Timers → Add Timer) that runs hourly and deletes ApiRateLimit records older than 2 hours: Delete Where WindowStart < AddHours(CurrDateTime(), -2). This keeps the table small and the rate limit lookups fast.
Why it's a problem: Using the same OutSystems endpoint URL directly in client documentation when an API Gateway is in use
How to avoid: Always expose the gateway URL in documentation, not the underlying OutSystems URL. The gateway URL is stable; the OutSystems URL may change during deployments or server migrations. Store the gateway URL as a Site Property for use in logging and documentation.
Best practices
- Never rely solely on OutSystems-level authentication for public APIs — place a gateway in front to provide DDoS protection, rate limiting, and API key lifecycle management.
- If implementing the database rate-limiting counter pattern, add an index on ApiRateLimit.ApiKeyId and ApiRateLimit.WindowStart to prevent this from becoming a performance bottleneck at scale.
- Use separate rate limit tiers for different API consumers — free tier (100 req/min), paid tier (1000 req/min), partner tier (unlimited). Store the limit on the ApiKey entity rather than hardcoding it.
- Set up a cleanup Timer to delete old ApiRateLimit records (older than 24 hours) — without cleanup the table grows unbounded and slows the rate limit lookups.
- For AWS API Gateway integration, use AWS WAF alongside the gateway to block malicious traffic before it reaches your rate limit logic.
- Document your rate limits in the API's Swagger description so consumers know the limits before hitting them.
- Return the X-RateLimit-Limit and X-RateLimit-Remaining headers on every response (not just 429s) so consumers can proactively throttle their own request rates.
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I'm running an OutSystems 11 application that exposes REST APIs to external partners. I need to implement rate limiting without an external API gateway. My OutSystems module has an ApiKey entity and an OnAuthentication callback already set up for API key validation. Help me design: 1) the ApiRateLimit entity schema with support for per-key rate limit tiers, 2) the rate limiting logic to add to OnAuthentication, 3) a cleanup Timer to delete old records, and 4) how to return the remaining quota in response headers.
I'm integrating AWS API Gateway in front of my OutSystems exposed REST API. The OutSystems API uses X-API-Key header authentication. I want AWS API Gateway to validate API keys using Usage Plans, and then pass through to OutSystems. Should I keep OutSystems authentication enabled or disable it? How should I configure the HTTP Proxy integration in AWS API Gateway to pass path parameters and query strings through correctly to the OutSystems endpoint format: https://server/Module/rest/v1/resource/{id}?
Frequently asked questions
Does OutSystems have any built-in rate limiting for exposed REST APIs?
No. OutSystems does not include application-level rate limiting for exposed REST APIs in either O11 or ODC. ODC has platform-level adaptive throttling for management APIs (100 req/min), but this does not apply to application endpoints you create. You must implement rate limiting either in the OnAuthentication callback using a counter entity pattern, or by placing an external API gateway (AWS, Kong, Apigee, Azure APIM) in front of your OutSystems endpoints.
What is the recommended API gateway for OutSystems Cloud deployments?
AWS API Gateway is the natural choice since OutSystems Cloud runs on AWS infrastructure — the two services are co-located in the same AWS regions, minimizing latency. For organizations already on Azure, Azure API Management integrates well with O11 deployments on Azure. For self-hosted O11, Kong (open-source) or nginx with lua-resty-limit-req are cost-effective options.
Can I implement rate limiting that persists across multiple OutSystems front-end servers in a farm setup?
The entity-based counter pattern in this tutorial uses the OutSystems database as a shared store, so it works correctly across multiple front-end servers in an O11 farm — all servers read from and write to the same database. The concern is write contention: at very high request rates (thousands per second), concurrent updates to the same rate limit window record can cause database locking. For those volumes, use an external gateway with a dedicated rate limiting backend (Redis, DynamoDB).
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation