.client file defines the UI, the .projection file defines the data/API layer, and customizations live in a separate Cust layer using @Override (merge changes) or @Overtake (replace entirely) annotations. Projections double as OData v4 REST endpoints for external integration..client files are atomic -- they cannot be partially overridden with @Override and must be fully rewritten with @Overtake. Lobby element creation still requires Enterprise Explorer, not Aurena.@Override and @Overtake -- @Override merges your changes with the base layer, while @Overtake completely replaces the original definition. Using @Overtake when @Override would suffice makes upgrades harder. [src2]/main/ Aurena projections; Basic Auth available only for Integration-category projections on the /int/ path. [src5, src6]IFS Cloud is a cloud-native ERP platform built on a component-based architecture. The Aurena client is the modern web-based UI (replacing the legacy Enterprise Explorer / Java-based client), built using a declarative domain-specific language called Marble. Aurena uses a clear separation between the client layer (UI definition in .client files) and the projection layer (data/API definition in .projection files). The underlying architecture generates two deployable packages from these models: the _CPI package (client page definition) and the _SVC package (projection/service logic). The projection services are exposed as OData v4 endpoints, making every Aurena projection inherently a REST API. [src1, src3]
| Property | Value |
|---|---|
| Vendor | IFS |
| System | IFS Cloud 24R2 (Aurena client) |
| API Surface | OData v4 (Projections as REST endpoints) |
| Current API Version | 24R2 (November 2024 release) |
| Editions Covered | All IFS Cloud editions |
| Deployment | Cloud (SaaS) / Managed Cloud |
| API Docs | IFS Technical Documentation - Extensibility |
| Status | GA |
IFS Aurena extensibility spans both UI customization and API integration. Every projection defined in Marble is automatically exposed as an OData v4 service endpoint, which means UI extensibility and API extensibility are the same thing. [src1, src4]
| API Surface | Protocol | Best For | Max Records/Request | Rate Limit | Real-time? | Bulk? |
|---|---|---|---|---|---|---|
| OData v4 Projections (Main) | HTTPS/JSON OData | Aurena pages + external OAuth integrations | Configurable via $top | Server-managed | Yes | Paged |
| OData v4 Projections (Integration) | HTTPS/JSON OData | Middleware / iPaaS integrations (Basic Auth) | Configurable via $top | Server-managed | Yes | Paged |
| Custom Projections | HTTPS/JSON OData | New business entities, custom logic | Same as standard | Shared pool | Yes | Paged |
| Projection Actions | HTTPS/JSON POST | Business operations (approve, release) | N/A (action-based) | Shared | Yes | N/A |
| Push Notifications / Business Events | HTTP callbacks | Real-time outbound event detection | N/A | N/A | Yes | N/A |
| IFS REST Task (Workflows) | HTTPS/JSON | Calling external REST APIs from IFS workflows | N/A | Per-workflow | Yes | N/A |
IFS Cloud does not publish hard numeric API rate limits. Rate management is handled at the infrastructure level through server-side throttling, connection pooling, and tenant isolation.
| Limit Type | Value | Applies To | Notes |
|---|---|---|---|
| Max records per query page | Configurable via $top | All OData projections | Default page size varies by projection; use $top and $skip for pagination [src4] |
| Max $expand depth | Limited (implementation-dependent) | OData queries | Deep nesting may hit server limits or timeout |
| Max request body size | Server-configured (typically 10-50 MB) | POST/PUT operations | Depends on IFS Cloud deployment configuration |
| Concurrent OData connections | Deployment-dependent | Per tenant | Managed by IFS Cloud infrastructure |
| Limit Type | Value | Window | Edition Differences |
|---|---|---|---|
| API request throughput | Server-managed (no published hard limit) | Continuous | Infrastructure-level throttling [src5] |
| Concurrent user sessions | License-dependent | Per deployment | Check IFS licensing terms |
| Workflow REST Task calls | Per-workflow frequency limits | Per execution | Configurable in workflow designer; new in 24R1 [src8] |
| Integration projection sessions | Deployment-dependent | Per tenant | Separate connection pooling on /int/ path |
IFS Cloud uses IFS IAM (Identity and Access Manager) as its OAuth 2.0 authorization server. The authentication method depends on whether you access projections through the Main application path or the Integration path. [src5, src6]
| Flow | Use When | Token Lifetime | Refresh? | Notes |
|---|---|---|---|---|
| OAuth 2.0 / OpenID Connect | All Aurena /main/ projections | Configurable in IFS IAM | Yes | Required for standard Aurena projections [src5] |
| Basic Auth (Integration category) | Middleware / iPaaS via /int/ path | Session-based | No | Only for Integration-category projections [src5] |
| Client Credentials (OAuth 2.0) | Server-to-server automation | Configurable | Yes | Register application in IFS IAM [src6] |
| Authorization Code (OAuth 2.0) | User-context interactive apps | Configurable | Yes | Redirect-based flow [src6] |
/main/ require OAuth 2.0. If your middleware only supports Basic Auth, change the projection category to "Integration" in API Explorer (moves it to /int/ path). [src5]-Cust suffix. Modifying base layer files directly is unsupported -- IFS upgrades overwrite them. [src2, src7].client files are atomic -- you must @Overtake the entire definition. [src2].plsvc file for business logic. [src1]START -- User needs to extend or integrate with IFS Cloud (Aurena)
|-- What type of extensibility?
| |-- UI Customization (change existing pages)
| | |-- Add fields? --> @Override in .client-Cust + .projection-Cust [src2]
| | |-- Change behavior (commands)? --> @Overtake in .client-Cust [src2]
| | |-- New page? --> New .client + .projection + .plsvc in Cust layer [src2]
| |-- API / Data Integration (external systems)
| | |-- Read data? --> Expose projection via API Explorer as OData [src4]
| | |-- Write data? --> POST/PUT to projection OData endpoint [src4]
| | |-- Business action? --> POST to projection action endpoint [src4]
| | |-- Auth: OAuth capable? --> /main/ path [src5]
| | |-- Auth: Basic Auth only? --> Set to Integration, /int/ path [src5]
| |-- Dashboard / Lobby
| | |-- Design in Enterprise Explorer [src1]
| | |-- Auto-appears in Aurena after configuration
| |-- Workflow Integration (24R1+)
| |-- Call external API? --> IFS REST Task [src8]
| |-- Event-driven outbound? --> Business Events
|-- Customization layer?
| |-- Cust layer (-Cust suffix) --> Customer changes [src2, src7]
| |-- Ext layer (-Ext suffix) --> ISV/partner extensions [src3]
| |-- Base layer --> Never modify [src7]
|-- Deploy as IFS Cloud component, test in sandbox first [src3]
| Operation | Method | Endpoint Pattern | Payload | Notes |
|---|---|---|---|---|
| Query records | GET | /main/ifsapplications/projection/v1/{Projection}.svc/{EntitySet} | N/A | OData v4: $filter, $select, $expand, $top, $skip [src4] |
| Get single record | GET | /main/.../v1/{Projection}.svc/{EntitySet}(keys) | N/A | Key fields in parentheses |
| Create record | POST | /main/.../v1/{Projection}.svc/{EntitySet} | JSON | Standard OData POST |
| Update record | PATCH | /main/.../v1/{Projection}.svc/{EntitySet}(keys) | JSON | Partial update via PATCH |
| Delete record | DELETE | /main/.../v1/{Projection}.svc/{EntitySet}(keys) | N/A | Not all entities support delete |
| Execute action | POST | /main/.../v1/{Projection}.svc/{ActionName} | JSON params | Actions defined in projection |
| Integration path | GET/POST | /int/ifsapplications/projection/v1/{Projection}.svc/{EntitySet} | JSON/N/A | Integration-category only [src5] |
| API Explorer | Browser | Solution Manager > Integration > API Explorer | N/A | Browse projections, enable for integration |
| Page registration | N/A | Solution Manager > Navigator | N/A | Register custom .client pages |
Navigate to Solution Manager > Integration > API Explorer. Browse or search for projections, view entity sets, actions, and functions. Enable for Integration to allow Basic Auth access. [src4]
Verify: Projection appears in API Explorer with entity sets listed.
Register your application in IFS IAM. For server-to-server, use client credentials flow. [src5, src6]
# Discover OAuth endpoints
curl -s "https://yourinstance.ifs.cloud/.well-known/openid-configuration" | python3 -m json.tool
# Obtain token using client credentials flow
curl -X POST "https://yourinstance.ifs.cloud/auth/realms/ifs/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET" \
-d "scope=openid"
Verify: Response contains an access_token field.
Use standard OData v4 query syntax against the projection endpoint. [src4]
# Query with OAuth
curl -X GET "https://yourinstance.ifs.cloud/main/ifsapplications/projection/v1/CustomerOrderHandling.svc/CustomerOrderSet?\$filter=State eq 'Planned'&\$top=50" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json"
# Query with Basic Auth (Integration path)
curl -X GET "https://yourinstance.ifs.cloud/int/ifsapplications/projection/v1/CustomerOrderHandling.svc/CustomerOrderSet?\$top=10" \
-u "username:password" \
-H "Accept: application/json"
Verify: Response contains @odata.context and a value array.
When existing projections do not expose the data shape you need, create a custom projection using IFS Developer Studio in the Cust layer. [src1, src2]
-- File: YourComponent-Cust.projection (Marble DSL)
projection MyCustomIntegrationHandling;
component YourComponent;
layer Cust;
description "Custom projection for external integration";
entityset MyCustomEntitySet for MyCustomEntity;
@Override
entity MyCustomEntity {
attribute OrderNo Text;
attribute CustomerNo Text;
attribute TotalAmount Number;
attribute Status Enumeration(CustomerOrderState);
}
action ApproveOrder {
initialcheck none;
parameter OrderNo Text;
}
Verify: After deploying, the projection appears in API Explorer.
Use @Override in a Cust-layer .client file. [src2]
-- File: CustomerOrderHandling-Cust.client (Marble DSL)
client CustomerOrderHandlingCust;
component ORDER;
layer Cust;
@Override
page CustomerOrderForm using CustomerOrderSet {
group CustomFieldsGroup after GeneralGroup {
label = "Custom Fields";
field MyCustomField {
label = "Custom Reference";
size = Medium;
}
}
}
Verify: The custom field group appears on the Customer Order page in Aurena.
Parse OData error responses and implement retry logic. [src4, src5]
import requests, time
def ifs_odata_request(instance_url, token, projection, entity_set,
params=None, max_retries=3):
url = (f"{instance_url}/main/ifsapplications/projection/v1/"
f"{projection}.svc/{entity_set}")
headers = {"Authorization": f"Bearer {token}",
"Accept": "application/json"}
for attempt in range(max_retries):
resp = requests.get(url, headers=headers, params=params, timeout=30)
if resp.status_code == 200:
return resp.json().get("value", [])
if resp.status_code == 401:
raise Exception("Token expired or invalid")
if resp.status_code == 403:
raise Exception("Valid token but insufficient projection access")
if resp.status_code in (500, 502, 503):
time.sleep(min(2 ** attempt * 2, 30))
continue
raise Exception(f"OData error {resp.status_code}: {resp.text[:500]}")
raise Exception("Max retries exceeded")
Verify: Test with an invalid projection name to confirm error parsing.
# Input: IFS Cloud instance URL, OAuth token
# Output: All customer orders matching filter across all pages
import requests
def get_all_orders(instance_url, token, state="Planned", page_size=100):
headers = {"Authorization": f"Bearer {token}", "Accept": "application/json"}
all_orders, skip = [], 0
while True:
url = (f"{instance_url}/main/ifsapplications/projection/v1/"
f"CustomerOrderHandling.svc/CustomerOrderSet"
f"?$filter=State eq '{state}'&$top={page_size}&$skip={skip}")
resp = requests.get(url, headers=headers, timeout=30)
resp.raise_for_status()
records = resp.json().get("value", [])
if not records:
break
all_orders.extend(records)
if len(records) < page_size:
break
skip += page_size
return all_orders
// Input: IFS Cloud instance URL, OAuth token, order data
// Output: Created record or error
// npm install [email protected]
const axios = require("axios");
async function createOrder(instanceUrl, token, orderData) {
const url = `${instanceUrl}/main/ifsapplications/projection/v1/CustomerOrderHandling.svc/CustomerOrderSet`;
try {
const resp = await axios.post(url, orderData, {
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
});
console.log("Order created:", resp.data.OrderNo);
return resp.data;
} catch (err) {
if (err.response?.status === 403) console.error("Check IFS permission sets");
throw err;
}
}
# Test OAuth (Main path)
curl -s "https://yourinstance.ifs.cloud/main/ifsapplications/projection/v1/CustomerHandling.svc/CustomerSet?\$top=5" \
-H "Authorization: Bearer $TOKEN" -H "Accept: application/json" | python3 -m json.tool
# Test Basic Auth (Integration path)
curl -s "https://yourinstance.ifs.cloud/int/ifsapplications/projection/v1/CustomerHandling.svc/CustomerSet?\$top=5" \
-u "user:pass" -H "Accept: application/json" | python3 -m json.tool
# OData metadata
curl -s "https://yourinstance.ifs.cloud/main/ifsapplications/projection/v1/CustomerHandling.svc/\$metadata" \
-H "Authorization: Bearer $TOKEN" -H "Accept: application/xml"
| IFS Aurena Field | OData Representation | Type | Transform | Gotcha |
|---|---|---|---|---|
| Standard attribute | Direct JSON property | String/Number/Date | None | Field names case-sensitive, match Marble attribute names [src4] |
| Enumeration fields | String value | String | Display value in OData | Filter by display value, not internal enum code [src4] |
| Reference fields (LOV) | String (key value) | String | Key only, not description | Use $expand for descriptions |
| Date fields | ISO 8601 string | DateTimeOffset | YYYY-MM-DDTHH:MM:SSZ | Time zone depends on instance config |
| Computed/virtual fields | Read-only JSON property | Varies | Defined by fetch attribute | Cannot be updated via API [src1] |
| Custom fields (Cust layer) | Same as standard | Varies | Auto-exposed if in projection | Must be in .projection-Cust to appear in OData [src2] |
State eq 'Planned'), not the internal database code. [src4]| Code | Meaning | Cause | Resolution |
|---|---|---|---|
| 400 | Validation error | Invalid field value, missing required field, or malformed OData query | Parse OData error body for field-level details [src4] |
| 401 | Authentication failure | Expired OAuth token, invalid credentials | Re-authenticate; for /main/ use OAuth, for /int/ use Basic Auth [src5] |
| 403 | Insufficient permissions | Valid token, user lacks projection access | Check IFS permission sets [src5, src6] |
| 404 | Not found | Wrong projection name, entity set name, or record key | Verify names in API Explorer; case-sensitive |
| 500 | Server-side failure | PL/SQL error in projection logic or DB constraint | Check IFS Cloud application server logs |
| 502/503 | Service unavailable | Deployment in progress or overload | Retry with exponential backoff |
Implement token refresh before each request. Use refresh tokens. [src5, src6]Use @Override where possible. Run Configuration Analyzer before upgrades. [src2, src7]Create and assign IFS permission set for the projection. [src5]Audit Integration-category projections; restrict via firewall rules. [src5]-- BAD -- base layer modification will be overwritten on upgrade
-- File: CustomerOrderHandling.client (base layer)
client CustomerOrderHandling;
component ORDER;
layer Core;
page CustomerOrderForm using CustomerOrderSet {
field MyCustomField; -- Lost on next IFS upgrade
}
-- GOOD -- Cust layer survives upgrades
-- File: CustomerOrderHandling-Cust.client
client CustomerOrderHandlingCust;
component ORDER;
layer Cust;
@Override
page CustomerOrderForm using CustomerOrderSet {
field MyCustomField after ExistingField {
label = "Custom Reference";
}
}
-- BAD -- commands are atomic, @Override fails
@Override
command ApproveOrderCommand {
label = "Custom Approve"; -- Deployment error
}
-- GOOD -- @Overtake replaces entire command
@Overtake
command ApproveOrderCommand {
label = "Custom Approve";
enabled = [Status = "Planned"];
execute {
call ApproveOrder(OrderNo);
refresh;
success("Order approved successfully");
}
}
# BAD -- Basic Auth rejected on /main/ path
url = "https://instance.ifs.cloud/main/.../CustomerHandling.svc/CustomerSet"
response = requests.get(url, auth=("user", "pass")) # 401
# GOOD -- /int/ path accepts Basic Auth for Integration-category projections
url = "https://instance.ifs.cloud/int/.../CustomerHandling.svc/CustomerSet"
response = requests.get(url, auth=("integration_user", "password"))
Only use @Overtake for commands (required) and fundamental behavior changes. Use @Override for additive changes. [src2]Always modify both .client-Cust and .projection-Cust together. [src2]Run Configuration Analyzer, Lobby Analyzer, and Update Analyzer before accepting upgrades. [src7]Maintain register of Integration-category projections; prefer OAuth. [src5]Create PL/SQL implementation for every custom action in the .plsvc file. [src1]Test each OData feature in API Explorer before building integration. [src4]# Test OAuth token
curl -s "https://yourinstance.ifs.cloud/main/ifsapplications/projection/v1/" \
-H "Authorization: Bearer $TOKEN" -H "Accept: application/json"
# Get OData metadata for a projection
curl -s "https://yourinstance.ifs.cloud/main/ifsapplications/projection/v1/CustomerHandling.svc/\$metadata" \
-H "Authorization: Bearer $TOKEN" -H "Accept: application/xml" | head -50
# Test Basic Auth on Integration path
curl -s "https://yourinstance.ifs.cloud/int/ifsapplications/projection/v1/CustomerHandling.svc/CustomerSet?\$top=1" \
-u "integration_user:password" -H "Accept: application/json"
# Discover OpenID Connect configuration
curl -s "https://yourinstance.ifs.cloud/.well-known/openid-configuration" | python3 -m json.tool
# Verify projection entity schema
curl -s "https://yourinstance.ifs.cloud/main/ifsapplications/projection/v1/CustomerOrderHandling.svc/\$metadata" \
-H "Authorization: Bearer $TOKEN"
| IFS Cloud Version | Release Date | Status | Key Extensibility Changes | Migration Notes |
|---|---|---|---|---|
| 24R2 | 2024-11 | Current | Enhanced OAuth in IFS REST Task; externalized URL configuration | Audit workflow REST tasks for new OAuth options [src8] |
| 24R1 | 2024-05 | Supported | External REST API calls from workflows (IFS REST Task) | New capability, no breaking changes [src8] |
| 23R2 | 2023-11 | Supported | Solution Manager enhancements; improved API Explorer | N/A |
| 23R1 | 2023-05 | Supported | Custom Projection assistant in Solution Manager | Low-code projection creation |
| 22R2 | 2022-11 | Legacy | Projection-level security enhancements | Consider upgrading |
| 22R1 | 2022-05 | EOL | Baseline for current extensibility model | Minimum version for full support |
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Extending IFS Aurena pages with custom fields, groups, or commands | Building separate web apps that read IFS data | Direct OData API integration |
| Creating custom projections for OData APIs | Needing hard numeric rate limits and SLA-guaranteed throughput | IFS Connect or middleware with rate management |
| Configuring lobby dashboards with KPIs | Needing real-time streaming / CDC | IFS Business Events + external event processing |
| Non-intrusive customizations surviving upgrades | Modifying core IFS PL/SQL base packages | IFS Developer Studio extension layer with partnership |
| Middleware integration via Basic Auth on /int/ path | Complex multi-step orchestration across systems | iPaaS (MuleSoft, Boomi) with IFS OData connector |