Skip to main content
RapidDev - Software Development Agency
outsystems-tutorial

How to Expose REST APIs from OutSystems Applications

To expose a REST API from OutSystems, go to Logic tab → Integrations → REST → right-click → Expose REST API. Name the API (e.g., 'v1'), add methods via right-click → Add REST API Method, set the HTTP verb, and implement logic like any Server Action. After 1-Click Publish, the endpoint is live at https://<server>/<module>/rest/<api_name>/<method_name> with auto-generated Swagger documentation.

What you'll learn

  • Create a versioned REST API node in Service Studio and add method endpoints
  • Configure HTTP verbs, URL parameters, request/response body structures, and status codes
  • Implement authentication with API key and Basic Auth using the OnAuthentication callback
  • Access auto-generated OpenAPI/Swagger documentation for your exposed API
  • Handle CORS, versioning, and the differences between O11 and ODC endpoint URL formats
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate8 min read20-25 minOutSystems 11 and ODCMarch 2026RapidDev Engineering Team
TL;DR

To expose a REST API from OutSystems, go to Logic tab → Integrations → REST → right-click → Expose REST API. Name the API (e.g., 'v1'), add methods via right-click → Add REST API Method, set the HTTP verb, and implement logic like any Server Action. After 1-Click Publish, the endpoint is live at https://<server>/<module>/rest/<api_name>/<method_name> with auto-generated Swagger documentation.

Publishing OutSystems Logic as a REST API

Any OutSystems module can expose its Server Actions as REST endpoints with a few clicks. This enables mobile apps, third-party services, and other OutSystems modules to communicate with your application over HTTP. Service Studio auto-generates the OpenAPI documentation, handles JSON serialization, and provides built-in authentication hooks. This tutorial covers creating GET and POST endpoints, securing them, and consuming the auto-generated docs.

Prerequisites

  • OutSystems 11 module or ODC app with at least one Server Action and Entity already created
  • Basic understanding of REST concepts (HTTP verbs, request/response bodies, status codes)
  • Service Studio installed and connected to your environment (O11), or ODC Studio open in browser
  • Understanding of OutSystems Structures (used to define request/response bodies)

Step-by-step guide

1

Create the Expose REST API node

In Service Studio, click the Logic tab. Expand Integrations. Right-click REST and select Expose REST API. A dialog asks for the API Name — use a version prefix such as v1 or ProductsAPI_v1. This name appears in the endpoint URL and the generated Swagger documentation title. Click OK. The new API node appears under Logic tab → Integrations → REST with a globe-and-arrow icon indicating it is an exposed API.

Expected result: The REST API node appears under Logic tab → Integrations → REST. Its properties pane shows Name, Description, Authentication, and Documentation fields.

2

Add a GET endpoint with a URL parameter

Right-click your new REST API node → Add REST API Method. Name the method GetProduct. In the Properties panel on the right: - Set HTTP Method to GET - Set URL Path to /products/{ProductId} — the {ProductId} placeholder auto-creates an input parameter - Set Description (this text appears in the generated docs) Service Studio auto-creates a ProductId input parameter (Long Integer, matching the Entity Identifier type). Add an output parameter Product of your Product structure type to represent the response body. Inside the method's action flow, add an Aggregate or GetProduct server action: Start → GetProductById (aggregate with filter Product.Id = ProductId) → Assign (Product = GetProductById.List.Current.Product) → End

Expected result: The GET /products/{ProductId} method is defined. TrueChange shows no errors.

3

Add a POST endpoint to create records

Right-click the REST API node → Add REST API Method. Name it CreateProduct. Set HTTP Method to POST and URL Path to /products. Add a Request Body input parameter named ProductData of type Product_Request (a Structure you define under Data tab → Structures → right-click → Add Structure). Define attributes: Name (Text), Price (Decimal), CategoryId (Category Identifier). Add an output parameter CreatedId (Long Integer) for the new record's ID. Action flow: Start → CreateProduct (Entity Action, ProductData mapped to Entity attributes) → Assign (CreatedId = CreateProduct.CreatedId) → End For the HTTP response, set the HTTP Status Code output to 201 via an Assign: use the SetResponseStatusCode action from the HTTPRequestHandler reference module.

Expected result: The POST /products method is defined with a structured request body. The action creates a database record and returns the new ID.

4

Configure authentication with API key

Click your REST API node. In the Properties panel, set Authentication to Custom. An OnAuthentication action is automatically created under your REST API node. Open the OnAuthentication action. The action flow receives every inbound request before routing. Add the following logic: Start → GetRequestHeader (Name = "X-API-Key") → Assign (ApiKey = GetRequestHeader.Value) → GetApiKeyRecord (Aggregate: ApiKeyTable with filter ApiKeyTable.Key = ApiKey and ApiKeyTable.IsActive = True) → If (GetApiKeyRecord.List.Empty) → [True] Raise Exception (User Exception: InvalidAPIKey) → [False] End TrueChange will validate the exception type. The raised User Exception causes OutSystems to return a 403 Forbidden response automatically.

Expected result: Requests without a valid X-API-Key header receive a 403 response. Requests with a valid key proceed to the method handler.

5

Publish and access auto-generated Swagger docs

Press Ctrl+F5 (or click the 1-Click Publish button at the top of Service Studio) to publish your module. Wait for the Upload → Compile → Deploy cycle to complete in the 1-Click Publish tab. After publishing, right-click your REST API node → Open Documentation. Your default browser opens the Swagger UI for your API. The generated endpoint URL format is: O11: https://<your-server>/<module-name>/rest/<api-name>/<method-name> Example: https://myserver.outsystemscloud.com/ProductsModule/rest/v1/products ODC format (no module name): https://<app-domain>/rest/<api-name>/<method-name>

Expected result: The Swagger UI displays all methods with their parameters, request/response schemas, and try-it-out capability.

6

Enrich documentation with descriptions

Well-documented APIs are self-explanatory. Add Description properties throughout: - REST API node: enter the overall API description in the Description property (appears in Swagger info block) - Each method: enter what it does in the method's Description property - Each input/output parameter: enter the parameter description (appears as parameter description in Swagger) - Each Structure attribute: enter the field description After adding descriptions, 1-Click Publish again. Reload the Swagger documentation page — all descriptions now appear in the generated OpenAPI spec and Swagger UI.

Expected result: The Swagger documentation clearly describes each endpoint, its parameters, and expected responses.

7

Handle versioning for breaking changes

When you need to make breaking changes to an existing API, create a new versioned API node rather than modifying the existing one. Right-click REST → Expose REST API → name it v2. Add the updated methods under v2. Keep the v1 API active for backward compatibility. Inform consumers of the deprecation timeline using the v1 API's Description property. Both v1 and v2 are active simultaneously after publishing. For non-breaking additions (new optional parameters, new endpoints), you can add them to the existing v1 API without breaking existing consumers.

Expected result: Both v1 and v2 API nodes are visible under Logic tab → Integrations → REST. Each has its own URL namespace and documentation page.

Complete working example

ExposedAPI_OnAuthentication_pseudocode.txt
1/* OnAuthentication Action Custom API Key Validation
2 Called automatically before every inbound REST request.
3 Raises InvalidAPIKey User Exception to reject with 403.
4*/
5
6Start
7 |
8 v
9[GetRequestHeader] -- HTTPRequestHandler system action
10 Name = "X-API-Key"
11 Value = (output) ApiKeyHeader
12 |
13 v
14[GetApiKeyRecord] -- Aggregate
15 Source: ApiKey entity
16 Filter: ApiKey.KeyValue = ApiKeyHeader
17 AND ApiKey.IsActive = True
18 MaxRecords: 1
19 |
20 v
21[If] Condition: GetApiKeyRecord.List.Empty
22 |
23 |-- [True] ->
24 | [Raise Exception]
25 | ExceptionType: InvalidAPIKey (User Exception)
26 | -- returns HTTP 403 Forbidden automatically
27 |
28 |-- [False] ->
29 [Assign]
30 CurrentApiKeyId = GetApiKeyRecord.List.Current.ApiKey.Id
31 -- Optionally log usage for rate limiting/analytics
32 |
33 v
34 End
35
36/* Method: GetProduct
37 HTTP GET /products/{ProductId}
38 Returns: Product structure or 404
39*/
40
41Start
42 |
43 v
44[GetProductById] -- Aggregate
45 Source: Product
46 Filter: Product.Id = ProductId
47 MaxRecords: 1
48 |
49 v
50[If] Condition: GetProductById.List.Empty
51 |
52 |-- [True] ->
53 | [Raise Exception] ProductNotFound (User Exception)
54 | -- returns HTTP 404 Not Found
55 |
56 |-- [False] ->
57 [Assign]
58 Product = GetProductById.List.Current.Product
59 |
60 v
61 End

Common mistakes

Why it's a problem: Modifying an existing exposed API method signature (renaming parameters, changing types) without creating a new version

How to avoid: Breaking changes require a new versioned API (v2, v3). Add the new version node alongside the old one, migrate consumers gradually, then deprecate the old version. Changing a method in v1 silently breaks all consumers.

Why it's a problem: Using the Authentication = None setting on an exposed API in a production environment

How to avoid: Always set Authentication to Basic or Custom for APIs that access data or trigger business logic. Authentication = None means any request from the internet can call the endpoint without any credential check.

Why it's a problem: Returning full entity records with sensitive fields (passwords, internal IDs, audit timestamps) in API responses

How to avoid: Define a dedicated response Structure that contains only the fields consumers need. Map entity data to this structure with an Assign node before returning. Never return the raw Entity record as the output parameter type.

Why it's a problem: Not handling the case where a requested record does not exist in GET methods

How to avoid: Always check the Aggregate's List.Empty before accessing List.Current. If empty, raise a User Exception (e.g., RecordNotFound) — OutSystems converts this to a 4xx HTTP response, which is correct REST behavior.

Best practices

  • Always version your exposed APIs by including v1, v2, etc. in the API name so you can evolve the API without breaking existing consumers.
  • Use Structures to define request and response bodies — avoid using generic Text parameters for complex payloads.
  • Add Description properties to all API nodes, methods, and parameters. The auto-generated Swagger docs become the primary documentation for consumers.
  • Store API keys in a database entity with an IsActive flag — this lets you revoke individual keys without redeploying.
  • Never expose internal entity IDs directly as API parameters unless necessary — consider using a surrogate identifier or slug to prevent enumeration attacks.
  • Raise typed User Exceptions for error conditions (ProductNotFound, InvalidAPIKey) — OutSystems maps these to appropriate HTTP status codes in the response.
  • Test your exposed API with the Swagger UI after every publish to catch serialization mismatches before external consumers encounter them.
  • Configure CORS explicitly if you know the allowed origins — the default Access-Control-Allow-Origin: * is permissive and should be restricted in production.

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I'm exposing a REST API from OutSystems 11 for a Product catalog. My module has a Product entity with attributes: Id (Long Integer), Name (Text), Price (Decimal), CategoryId (Category Identifier), IsActive (Boolean). Help me design: 1) the URL structure for CRUD endpoints (GET list, GET by ID, POST create, PUT update, DELETE), 2) the request/response Structure definitions, and 3) the OnAuthentication logic for Bearer token validation.

OutSystems Prompt

In my OutSystems module, I have an exposed REST API named v1 with a method GetOrdersByCustomer (GET /orders?customerId={CustomerId}). The Aggregate returns a List of Order records. Write the complete action flow using arrow notation that: checks if the list is empty, maps the entity list to a response Structure list, and returns the list with a 200 status code. Also show the 404 flow if the customerId does not exist.

Frequently asked questions

What HTTP status codes does OutSystems return for raised exceptions in exposed REST APIs?

OutSystems maps exception types to HTTP status codes automatically: User Exceptions return 400 Bad Request, Security Exceptions return 401 Unauthorized, and Communication Exceptions return 500 Internal Server Error. To return a 404, raise a User Exception — the consumer receives the exception message in the response body. To return a custom status code such as 201, use SetResponseStatusCode from the HTTPRequestHandler extension.

Can I expose a REST API from an OutSystems module that is consumed by another OutSystems module?

Yes, but the preferred approach for inter-module communication within the same environment is Service Actions (Logic tab → Service Actions) because they are strongly typed, validated at design time by TrueChange, and do not incur HTTP overhead. Use exposed REST APIs when the consumer is an external system, a mobile app, or a module in a different environment.

How do I return a list of records from an exposed REST GET endpoint?

Define a response Structure and add an output parameter of type List of that Structure to your method. In the action flow, run an Aggregate, then loop through the results with a For Each node, building the output list with ListAppend. Alternatively, use an Assign node with the aggregate's list directly if the structure matches the entity shape. OutSystems serializes the list as a JSON array automatically.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.