# Complete Guide to REST API Error Handling in OutSystems

- Tool: OutSystems
- Difficulty: Intermediate
- Time required: 20-25 min
- Compatibility: OutSystems 11 and ODC
- Last updated: March 2026

## TL;DR

Every REST API call in OutSystems can fail. Add a Communication Exception handler to every Server Action that makes an external API call. Check HTTP status codes via the OnAfterResponse callback. Log all integration errors with the LogError system action. For transient failures, implement a retry pattern using a For loop with a counter and Wait node. Service Center → Monitoring → Integrations shows the complete request/response history for debugging.

## Why API Error Handling Is Non-Negotiable

External REST API calls can fail for many reasons: network timeouts, server overloads, authentication expiry, rate limits, or malformed responses. Without explicit error handling, a failed API call crashes your entire server action with an unhandled exception — the user sees a generic error page and you have no log entry to debug with. OutSystems provides several layers for catching and handling API errors: the exception handler in the action flow, the OnAfterResponse callback for HTTP-level inspection, and Service Center's integration logs for post-mortem analysis. This tutorial builds a robust error handling framework you can apply to any API integration.

## Before you start

- A consumed REST API already imported in Logic tab → Integrations → REST
- Basic understanding of OutSystems exception handling (User Exception, Communication Exception types)
- Familiarity with Server Actions and action flow notation
- Service Center access for reviewing integration logs (O11) or ODC Portal for traces (ODC)

## Step-by-step guide

### 1. Understand the OutSystems exception hierarchy for API calls

Before adding handlers, understand which exception types apply:

- Communication Exception: triggered when the HTTP request cannot complete — DNS failure, connection refused, TCP timeout. This is the most common error for external API calls.
- User Exception: can be raised manually in OnAfterResponse when you detect a bad HTTP status code (4xx, 5xx).
- Security Exception: raised when authentication/authorization fails — use this in OnAuthentication callbacks.
- AllExceptions: catch-all — use as a fallback after more specific handlers.

In OutSystems, unhandled exceptions bubble up to the nearest parent action and eventually to the screen, showing the default error page. Adding explicit handlers breaks this bubble and lets you control the user experience.

**Expected result:** You understand when each exception type fires and which to handle in which context.

### 2. Add a Communication Exception handler to every API-calling Server Action

Open the Server Action that calls your REST API method. In the action flow, right-click anywhere in the flow canvas → Add Exception Handler → select Communication Exception.

This creates a handler branch below the main flow. Inside the handler:
1. Add an Assign node to set meaningful output parameters: IsSuccess = False, ErrorMessage = "Unable to reach the service. Please try again later."
2. Add a LogError node (from System module via Manage Dependencies): Module = your module name, Message = "API call failed: " + CommException.Message, ErrorDetail = CommException.StackTrace
3. Add an End node

Action flow with handler:
Start → CallExternalAPI (REST method) → Assign (process response) → End
[Communication Exception] → Assign (IsSuccess=False, ErrorMessage) → LogError → End

**Expected result:** When the external API is unreachable, the action returns IsSuccess=False with a user-friendly message instead of crashing.

### 3. Inspect HTTP status codes in OnAfterResponse

The Communication Exception only fires for network-level failures. HTTP errors (401, 403, 404, 429, 500) from the server are NOT automatically raised as exceptions — OutSystems treats them as successful HTTP responses with an error status code. You must inspect the status code explicitly.

To do this:
1. Click your consumed REST API node in Logic tab → Integrations → REST
2. In Properties panel, find On After Response → New OnAfterResponse
3. Open the generated OnAfterResponse action

The action receives Response (HTTPResponse) with a StatusCode (Integer) attribute. Build this flow:

Start → Switch
  Case: Response.StatusCode = 401 → Raise Exception (UnauthorizedAccess, Security Exception)
  Case: Response.StatusCode = 403 → Raise Exception (Forbidden, User Exception)
  Case: Response.StatusCode = 404 → Raise Exception (ResourceNotFound, User Exception)
  Case: Response.StatusCode = 429 → Raise Exception (RateLimitExceeded, User Exception)
  Case: Response.StatusCode >= 500 → Raise Exception (ServerError, Communication Exception)
  Default → End (2xx responses pass through normally)

Create the named User Exceptions first: Logic tab → Exceptions → right-click → Add Exception.

**Expected result:** HTTP 4xx and 5xx responses are raised as typed exceptions that can be caught individually in calling actions.

### 4. Build a configurable retry pattern for transient failures

For transient failures (network blips, 503 Service Unavailable, 429 Rate Limited), implement a retry pattern rather than immediately failing.

Create a Server Action named CallAPIWithRetry with:
- Input: (whatever inputs your API method needs)
- Local variables: RetryCount (Integer, default 0), MaxRetries (Integer, default 3), RetryDelayMs (Integer, default 1000), LastError (Text), IsSuccess (Boolean)

Action flow:
Start → [For Loop: While RetryCount < MaxRetries] → CallExternalAPI → [On Success] Assign (IsSuccess=True) → Exit Loop
     → [Communication Exception or RateLimitExceeded Exception] → Assign (RetryCount = RetryCount + 1, LastError = exception message) → If (RetryCount >= MaxRetries) → [True] End [False] → Wait (RetryDelayMs milliseconds) → continue loop

The Wait action (from System module) pauses the server-side flow. For exponential backoff, set RetryDelayMs = RetryDelayMs * 2 on each iteration.

**Expected result:** Transient API failures are retried automatically up to 3 times with delays, improving reliability without user intervention.

### 5. Log all integration errors to Service Center

OutSystems has a built-in logging system accessible in Service Center → Monitoring → Error Log and → Integrations. To write to the Error Log from your exception handlers:

1. In Service Studio, go to Module → Manage Dependencies (Ctrl+Q)
2. Search for (System) module → expand → check LogError and LogWarning actions
3. Click Apply

In your exception handlers, add a LogError call:
- Module: your module name (Text literal, e.g., "ProductsModule")
- Message: descriptive error summary
- ErrorDetail: full exception stack trace or response body
- StackTrace: exception StackTrace attribute

For integration-specific logs, Service Center → Monitoring → Integrations shows every outgoing and incoming REST call with timestamp, URL, HTTP status, request headers, response body, and duration — invaluable for debugging without adding extra logging code.

**Expected result:** All API errors appear in Service Center → Monitoring → Error Log with enough context to diagnose root causes without re-running the scenario.

### 6. Define a consistent error response structure for exposed APIs

When exposing REST APIs, return consistent error responses that consumers can parse reliably. Create a Structure under Data tab → Structures → right-click → Add Structure. Name it ErrorResponse with attributes:
- Code (Text) — machine-readable error code, e.g., "RESOURCE_NOT_FOUND"
- Message (Text) — human-readable description
- Details (Text) — optional extended information

In your exposed API exception handlers (Logic tab → Exceptions per method):
- Catch each typed exception
- Assign the ErrorResponse structure
- Use SetResponseStatusCode to set the appropriate HTTP status
- The ErrorResponse output parameter is automatically serialized to JSON by OutSystems

Consumers then always receive: { "Code": "RESOURCE_NOT_FOUND", "Message": "Product with Id 42 was not found.", "Details": "" }

**Expected result:** Consumers of your exposed API receive structured, predictable error responses for all failure conditions.

## Complete code example

File: `APIErrorHandling_ServerAction_patterns.txt`

```plaintext
/* Pattern 1: Server Action with Communication Exception handler
   Input:  ProductId (Long Integer)
   Output: ProductData (Product_Response structure), IsSuccess (Boolean), ErrorMessage (Text)
*/

Start
  |
  v
[GetProductFromAPI]  -- REST method
  ProductId = ProductId
  |
  v
[Assign]
  ProductData  = GetProductFromAPI.Response
  IsSuccess    = True
  ErrorMessage = ""
  |
  v
End

[Exception Handler: Communication Exception]
  |
  v
[LogError]
  Module      = "ProductsModule"
  Message     = "GetProduct API call failed"
  ErrorDetail = CommException.ExceptionMessage
  |
  v
[Assign]
  IsSuccess    = False
  ErrorMessage = "Unable to reach the product service. Please try again."
  ProductData  = Default(Product_Response)
  |
  v
End

[Exception Handler: ResourceNotFound User Exception]
  |
  v
[Assign]
  IsSuccess    = False
  ErrorMessage = "Product not found."
  |
  v
End

/* Pattern 2: OnAfterResponse HTTP status code handler
   Attached to consumed REST API node.
   Input:  Response (HTTPResponse)
   Output: CustomizedResponse (HTTPResponse)
*/

Start
  |
  v
[Switch on Response.StatusCode]
  Case = 401  -->  [Raise Exception: UnauthorizedAccess (Security)]
  Case = 403  -->  [Raise Exception: Forbidden (User)]
  Case = 404  -->  [Raise Exception: ResourceNotFound (User)]
  Case = 429  -->  [Raise Exception: RateLimitExceeded (User)]
  Case >= 500 -->  [Raise Exception: ExternalServerError (Communication)]
  Default     -->  [Assign] CustomizedResponse = Response
  |
  v
End
```

## Common mistakes

- **Assuming HTTP 4xx and 5xx responses automatically trigger a Communication Exception** — undefined Fix: They do not. Communication Exceptions only fire for network-level failures (can't connect, timeout). You must check HTTP status codes explicitly in the OnAfterResponse callback and raise exceptions manually for error status codes.
- **Retrying on all exception types, including 401 Unauthorized and 400 Bad Request** — undefined Fix: Retrying on authentication failures burns retry budget and delays the eventual failure. Only retry on RateLimitExceeded (429), ServiceUnavailable (503), and Communication Exceptions. Check the exception type or status code before deciding to retry.
- **Showing raw exception messages or stack traces directly to end users** — undefined Fix: Log the full technical details to Service Center using LogError, and return a generic, user-friendly message to the screen. Exposing stack traces to users is a security risk and provides a poor user experience.
- **Forgetting to set the Timeout in Seconds property on REST methods, leaving the 100-second default** — undefined Fix: Click the REST method node → Properties panel → Timeout in Seconds. Set an appropriate value for the use case: 5-10 seconds for real-time UI calls, 30-60 seconds for batch operations, 5-15 seconds for health checks.

## Best practices

- Add a Communication Exception handler to every Server Action that makes an external REST API call — no exceptions (pun intended).
- Inspect HTTP status codes in the OnAfterResponse callback rather than checking them in each calling action — centralizing this logic means you fix it once.
- Only retry on transient errors (429, 503, Communication Exception). Never retry on 400, 401, or 403 — these are client errors that will not resolve themselves.
- Log every API error with the full exception message, stack trace, and relevant input parameters so you can diagnose failures from logs without reproducing them.
- Return user-friendly error messages to the UI (e.g., 'Service temporarily unavailable') rather than raw exception messages or HTTP status codes.
- Set realistic Timeout in Seconds values on each REST method — the default 100 seconds is too long for interactive requests. For read endpoints, 10-15 seconds is more appropriate.
- Monitor Service Center → Monitoring → Integrations daily during the first week after deploying an API integration — catch silent failures before users report them.
- Test error paths explicitly: temporarily change the endpoint URL to an invalid host to trigger a Communication Exception, and verify your handler produces the expected output.

## Frequently asked questions

### How do I see the full HTTP request and response body for a failed API call in OutSystems?

Go to Service Center → Monitoring → Integrations. Filter by module name and the time range of the failure. Each log entry shows the full request URL, headers, request body, HTTP status code, response body, and duration in milliseconds. This is the fastest debugging tool for API integration issues. In ODC, open ODC Portal → Monitoring → Traces and filter for the relevant app.

### Does OutSystems automatically retry failed API calls?

No. OutSystems does not have built-in retry logic for REST API calls. You must implement retry patterns manually in Server Actions using a loop with a counter and the Wait system action. This gives you full control over retry conditions, max attempts, and delay strategy — which is preferable since blind retries can worsen rate-limiting situations.

### What is the difference between handling errors in OnAfterResponse vs handling them in the calling Server Action?

OnAfterResponse runs once for the entire REST API and applies to every method call — it is the right place for protocol-level error handling like HTTP status code checking. The calling Server Action exception handler is method-call-specific and handles the business-level response to that error (showing a message, logging context-specific data, deciding whether to retry). Use both layers together for complete coverage.

---

Source: https://www.rapidevelopers.com/outsystems-tutorial/outsystems-api-error-handling
© RapidDev — https://www.rapidevelopers.com/outsystems-tutorial/outsystems-api-error-handling
