REST Integration Skill

SkillDocs & knowledge

Teaches your agent how to call external REST APIs from Mendix and pick the right integration approach.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the REST Integration Skill skill

About this capability

Call external REST APIs from Mendix, the three approaches (inline REST CALL, consumed REST client document, generated from OpenAPI) and how to choose. Use when integrating with any external REST API.

What this skill tells your AI

The instructions your AI receives, as published by mendixlabs/mxcli in .claude/skills/mendix/rest-client/SKILL.md and read by ahel’s review.

Use this skill when integrating with external REST APIs from Mendix.

Three Approaches

Mendix offers three ways to call REST APIs from microflows. Choose based on the use case:

ApproachWhen to UseArtifacts
OpenAPI importAPI has an OpenAPI 3.0 spec — auto-generate from the specREST client document generated in one command
REST Client (manual)No spec available, or need fine-grained controlREST client document + microflow
REST CALL (inline)One-off calls, quick prototyping, dynamic URLs, low-level HTTP controlMicroflow only

Both REST Client approaches can be combined with Data Transformers (Mendix 11.9+) and Import/Export Mappings to map between JSON and entities.

No API to call against yet — or one you would rather not depend on while building? mock-rest-apis covers standing up an endpoint you control and pointing the app at it.


Approach 0: OpenAPI Import (Fastest)

If the API has an OpenAPI 3.0 spec (JSON or YAML), generate the REST client in one command:

-- From a local file (relative to the .mpr file)
create or modify rest client CapitalModule.CapitalAPI (
  OpenAPI: 'specs/capital.json'
);

-- From a URL
create or modify rest client PetStoreModule.PetStoreAPI (
  OpenAPI: 'https://petstore3.swagger.io/api/v3/openapi.json'
);

-- Override the base URL (replaces servers[0].url from the spec)
create or modify rest client PetStoreModule.PetStoreStaging (
  OpenAPI: 'https://petstore3.swagger.io/api/v3/openapi.json',
  BaseUrl: 'https://staging.petstore.example.com/api/v3'
);

This generates:

  • All operations with correct HTTP method, path, parameters, headers, body, and response type
  • Resource groups based on OpenAPI tags
  • Basic auth if the spec declares it at the top level
  • The spec stored inside the document for Studio Pro parity

BaseUrl is optional. When omitted, servers[0].url from the spec is used — but only if it is absolute. A relative server URL (/api/v3) cannot be a BaseUrl: the import warns ("server URL … is relative and cannot be used as BaseUrl") and leaves the client without one, which fails at call time rather than at import time. Set BaseUrl explicitly in that case.

When provided, BaseUrl overrides the spec's value — useful when the spec points at production but you need to import against staging, a different version, or a local mock (see mock-rest-apis).

Preview without writing:

describe contract operation from openapi 'specs/capital.json';

After import: the REST client is ready to use with SEND REST REQUEST. No manual operation definition needed.


Approach 1: REST Client (Manual)

Define the API once as a REST client document, then call its operations from microflows.

Step 1 — Create the REST Client

create rest client Module.OpenMeteoAPI (
  BaseUrl: 'https://api.open-meteo.com/v1',
  authentication: none
)
{
  operation GetForecast {
    method: get,
    path: '/forecast',
    query: ($latitude: decimal, $longitude: decimal, $current: string),
    headers: ('Accept' = 'application/json'),
    timeout: 30,
    response: json as $WeatherJson
  }

  operation PostData {
    method: post,
    path: '/submit',
    headers: ('Content-Type' = 'application/json'),
    body: json from $JsonPayload,
    response: none
  }
};

Authentication

-- No authentication
authentication: none

-- Basic auth
authentication: basic (username: 'user', password: 'secret')

Body Types

-- JSON variable (Mendix expression stored on the operation)
body: json from $JsonPayload

-- String template with parameter placeholders
body: template '{ "name": "{name}", "value": {value} }'

-- Export mapping (entity → JSON, aligned with export mapping syntax)
body: mapping Module.RequestEntity {
  name = Name,
  email = Email,
}

There is no binary/file body here. A consumed operation's body is one of Rest$JsonBody, Rest$StringBody or Rest$ImplicitMappingBody, so a file document has nowhere to go. body: file from $Doc is refused as MDL-REST02 — it used to be written as a string body holding the literal text $Doc, which returns HTTP 200 with a 4-byte payload.

Upload binary from a microflow instead, which does have a binary body:

rest call post 'https://api.example.com/upload'
  header 'ContentType' = 'application/pdf'
  body binary $Doc/Contents
  returns response;

response: file as $Doc on an operation is unaffected — downloads work.

Response Types

-- Simple types (variable binding is at the call site, not stored on the operation)
response: json as $Result
response: string as $text
response: file as $Document
response: status as $Code
response: none

-- Import mapping (JSON → entity, aligned with import mapping syntax)
response: mapping Module.ResponseEntity {
  "Id" = "id",
  "status" = "status",
  create Module.Items_Response/Module.Item = items {
    "Sku" = "sku",
    "Quantity" = "quantity",
  }
}

mapping takes an ENTITY plus a body — never a mapping document. The name after mapping is the target entity, and the { ... } body lists the JSON fields; Mendix stores the result inline on the operation (Rest$ImplicitMappingResponseHandling). A consumed REST operation has nowhere to put a reference to a standalone import/export mapping — the metamodel defines only the inline handler and "no response handling". So this is wrong, and is now rejected by mxcli check as MDL-REST01:

response: mapping Module.IMM_Something   -- ✗ names a mapping document, no body

If you already have an import mapping document you want to reuse, call it from the microflow instead — send rest request with response: json as $Raw, then $Obj = import from mapping Module.IMM_Something($Raw).

Reaching a nested JSON member

A member several levels down is written with /, exactly as in a mapping document — you do NOT need an entity for the levels in between:

response: mapping Module.FlatProbe {
  "ItemId" = "id",
  "Title"  = "fields/Title",     -- reaches {"fields": {"Title": …}}
}

Mendix stores that as the pipe path (Object)|fields|Title. Before mxcli 0.19.x the inline serializer appended the text verbatim, producing (Object)|fields/Title — one member with a slash in its name — which passed mxcli check, exec, mx check and describe, and returned an empty column at runtime with no diagnostic. If you are on an older build, give each level its own entity instead.

An export body's nested elements

Body: MAPPING is an EXPORT mapping, and its nested elements must not be "create". mxcli writes them correctly, but it is worth knowing what a wrong one looks like, because mxbuild does not report it as a check error — it throws while loading the project, so the app cannot be opened at all:

System.AggregateException: … (Export Object Mappings cannot have
ObjectHandling set to 'Create')

Query parameters carry no type in the Mendix model: Rest$QueryParameter stores a name only. MDL still requires $name: Type for the sake of the grammar, but the type is dropped at write time, so describe re-emits every query parameter as String.

Step 2 — Call from a Microflow

create microflow Module.ACT_GetWeather ()
returns Module.WeatherInfo as $Weather
begin
  -- Call the REST client operation
  send rest request Module.OpenMeteoAPI.GetForecast;

  -- Extract response body from system variable
  declare $RawJson string = $latestHttpResponse/content;

  -- (Optional) Transform with JSLT
  $SimplifiedJson = transform $RawJson with Module.SimplifyWeather;

  -- Import into entity
  $Weather = import from mapping Module.IMM_Weather($SimplifiedJson);

  return $Weather;
end;
/

CRITICAL: After send rest request, the response is in $latestHttpResponse (System.HttpResponse):

  • $latestHttpResponse/content — response body (String)
  • $latestHttpResponse/StatusCode — HTTP status (Integer)

Show / Describe / Drop

show rest clients [in module];
describe rest client Module.ClientName;
drop rest client Module.ClientName;
create or modify rest client Module.ClientName ...  -- idempotent

Approach 2: REST CALL (Inline HTTP)

Call an HTTP endpoint directly from a microflow — no REST client document needed. Best for one-off calls, dynamic URLs, or low-level control.

-- Simple GET returning a string
$response = rest call get 'https://api.example.com/data'
  header Accept = 'application/json'
  timeout 30
  returns string;

-- GET with URL template parameters
$response = rest call get 'https://api.example.com/users/{1}' with (
  {1} = toString($UserId)
)
  header Accept = 'application/json'
  returns string;

-- POST with body
$response = rest call post 'https://api.example.com/items'
  header 'Content-Type' = 'application/json'
  body '{"name": "test"}'
  returns string;

-- With basic auth
$response = rest call get 'https://api.example.com/secure'
  auth basic 'username' password 'password'
  returns string;

-- With import mapping (JSON → entity)
$item = rest call get 'https://api.example.com/item/1'
  header Accept = 'application/json'
  returns mapping Module.IMM_Item as Module.Item;

-- Fire and forget
rest call delete 'https://api.example.com/item/1'
  returns nothing;

-- Error handling
$response = rest call get 'https://api.example.com/data'
  returns string
  on error continue;

Data Transformers (JSLT — Mendix 11.9+)

Transform complex JSON responses into simpler structures before import mapping.

-- Define the transformer
create data transformer Module.SimplifyWeather
source json '{"latitude": 52.52, "current": {"temperature_2m": 12.8, "wind_speed_10m": 18.3}}'
{
  jslt $$
{
  "temp": .current.temperature_2m,
  "wind": .current.wind_speed_10m,
  "lat": .latitude
}
  $$;
};

-- Use in a microflow
$SimplifiedJson = transform $RawJson with Module.SimplifyWeather;

Single-line JSLT: jslt '{ "temp": .current.temperature_2m }'; Multi-line JSLT: jslt $$ { ... } $$; (dollar-quoting, same as Java actions)

list data transformers [in module];
describe data transformer Module.Name;
drop data transformer Module.Name;

JSON Structures & Mappings

See json-structures-and-mappings for full reference. Quick summary:

-- JSON structure from snippet
create json structure Module.JSON_Weather
snippet '{"temp": 12.8, "wind": 18.3, "lat": 52.52}';

-- Non-persistent entity
create non-persistent entity Module.WeatherInfo (
  Temperature: decimal,
  WindSpeed: decimal,
  Latitude: decimal
);
/

-- Import mapping (JSON → entity)
create import mapping Module.IMM_Weather
  with json structure Module.JSON_Weather
{
  create Module.WeatherInfo {
    Temperature = temp,
    WindSpeed = wind,
    Latitude = lat
  }
};

-- Use in microflow
$Weather = import from mapping Module.IMM_Weather($JsonString);
$JsonOutput = export to mapping Module.EMM_Weather($WeatherEntity);

Complete Pipeline Example

Full example: call weather API → transform → import → show on page.

-- 1. Entity
create non-persistent entity Module.CurrentWeather (
  Temperature: decimal,
  WindSpeed: decimal,
  Latitude: decimal,
  ObservationTime: datetime
);
/

-- 2. Data Transformer (simplify API response)
create data transformer Module.SimplifyWeather
source json '{"latitude":52.52,"current":{"time":"2024-01-15T14:00","temperature_2m":12.8,"wind_speed_10m":18.3}}'
{
  jslt $$
{
  "temperature": .current.temperature_2m,
  "windSpeed": .current.wind_speed_10m,
  "latitude": .latitude,
  "observationTime": .current.time
}
  $$;
};

-- 3. JSON Structure + Import Mapping (for transformed output)
create json structure Module.JSON_Weather
snippet '{"temperature":12.8,"windSpeed":18.3,"latitude":52.52,"observationTime":"2024-01-15T14:00"}';

create import mapping Module.IMM_Weather
  with json structure Module.JSON_Weather
{
  create Module.CurrentWeather {
    Temperature = temperature,
    WindSpeed = windSpeed,
    Latitude = latitude,
    ObservationTime = observationTime
  }
};

-- 4. REST Client
create rest client Module.WeatherAPI (
  BaseUrl: 'https://api.open-meteo.com/v1',
  authentication: none
)
{
  operation GetCurrent {
    method: get,
    path: '/forecast',
    query: ($latitude: decimal, $longitude: decimal, $current: string),
    headers: ('Accept' = 'application/json'),
    response: json as $Result
  }
};

-- 5. Microflow (REST Client → Transform → Import)
create microflow Module.ACT_GetWeather ()
returns Module.CurrentWeather as $Weather
begin
  send rest request Module.WeatherAPI.GetCurrent;
  declare $RawJson string = $latestHttpResponse/content;
  $SimplifiedJson = transform $RawJson with Module.SimplifyWeather;
  $Weather = import from mapping Module.IMM_Weather($SimplifiedJson);
  return $Weather;
end;
/

Mendix Validation Rules

RuleErrorFix
Every operation MUST have an Accept headerCE7062Auto-added by serializer if missing
POST/PUT/PATCH MUST have a bodyCE7064Auto-added by serializer (empty JSON body)
Template placeholders must match parametersCE7056{name} requires a parameter named name
No custom error handling on SEND REST REQUESTCE6035Always uses abort-on-error semantics
Data Transformer requires 11.9+version checkcheckFeature("integration", "data_transformer", ...)

BSON Types Reference

MDL ConceptBSON Type
REST client documentrest$ConsumedRestService
Operationrest$RestOperation
GET/DELETE methodrest$RestOperationMethodWithoutBody
POST/PUT/PATCH methodrest$RestOperationMethodWithBody
JSON bodyrest$JsonBody
Template bodyrest$StringBody
Export mapping bodyrest$ImplicitMappingBody
No responserest$NoResponseHandling
Import mapping responserest$ImplicitMappingResponseHandling
Headerrest$HeaderWithValueTemplate
Path parameterrest$OperationParameter
Query parameterrest$QueryParameter
Data transformerDataTransformers$DataTransformer
JSLT stepDataTransformers$JsltAction
Transform actionmicroflows$TransformJsonAction
Send request actionmicroflows$RestOperationCallAction
Inline HTTP actionmicroflows$RestCallAction

Signals

GitHub stars
122
Forks
49
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
rest-client
Source
github.com/mendixlabs/mxcli