AI Message Mappers
A Message Mapper reshapes the JSON payload flowing through one operation of a published API — in one direction only. It exists to bridge the gap between how you want an agent to send/receive data and how the upstream API actually expects it, without asking the upstream to change.
direction: "request"— reshapes what the caller (agent) sends into what the upstream API actually expects.direction: "response"— reshapes what the upstream API returns into what the caller expects.
Each mapper applies to exactly one OpenAPI operationId, in exactly one direction — you can't have two request mappers (or two response mappers) for the same operation; edit the existing one instead.
Design-time AI, runtime determinism
This is the important safety property: the AI is only ever used at save time, to propose a transform_spec from a sample pair you provide. Once saved, every real request runs that expression through a deterministic, sandboxed evaluator (the same safe_eval engine Agent Builder's own transform node uses) — never a live LLM call on the request hot path.
transform_spec is a single Python-expression string, evaluated with one bound name, input (the payload being reshaped):
{"petName": input["name"], "petAge": input["age"]}
The evaluator only permits:
- Dict and list literals
- Indexing/subscripting (
input["key"],input["list"][0]) - Arithmetic, comparison, and boolean operators, and ternary expressions
- A fixed whitelist of built-ins:
abs, len, max, min, round, sum, any, all, zip, enumerate, map, filter, sorted, reversed, list, dict, set, tuple, bool, int, str, float
It does not permit attribute or method calls (no .upper(), no .strip()), imports, comprehensions, lambdas, function/class definitions, or any name other than input.
Endpoints
All under /api/v1/api-definitions/{api_definition_id}:
| Endpoint | Description |
|---|---|
GET /mappers | List all message mappers for this API |
POST /mappers | Create a mapper — operation_id, direction, optional source_sample/target_sample/transform_spec, enabled, test_cases |
GET /mappers/{mapper_id} | Get one mapper |
PUT /mappers/{mapper_id} | Update a mapper (partial — only provided fields change) |
DELETE /mappers/{mapper_id} | Delete a mapper |
POST /mappers/generate | AI-assisted: propose a transform_spec from a source_sample/target_sample pair. Doesn't save anything — review and save the proposal like any other field. |
POST /mappers/{mapper_id}/test | Run the mapper's transform_spec against its saved test_cases and record pass/fail |
Creating, editing, or deleting a mapper requires the api_developer, lifecycle_manager, space_admin, or admin role on the parent API (see Catalogs, Spaces & Roles).
Generating a mapper
POST /mappers/generate
{
"operation_id": "createPet",
"direction": "request",
"source_sample": { "name": "Rex", "age": 3 },
"target_sample": { "petName": "Rex", "petAge": 3 }
}{ "transform_spec": "{\"petName\": input[\"name\"], \"petAge\": input[\"age\"]}", "warning": null }If the LLM call itself fails (no default model configured, provider error), you get a 502 telling you to configure a default model or write the expression by hand — mapper generation isn't scoped to any API Space's own LLM config (API Spaces intentionally carry none), it always uses the instance-wide default model.
The proposal is also dry-run against your source_sample before being returned, so an obviously broken expression is flagged immediately rather than at test time.
Testing a mapper
POST /mappers/{mapper_id}/test{
"all_passed": true,
"results": [
{ "index": 0, "passed": true, "actual_output": {"petName": "Rex", "petAge": 3}, "expected_output": {"petName": "Rex", "petAge": 3} }
]
}Each entry in test_cases is {"input": ..., "expected_output": ...}. The mapper needs both a saved transform_spec and at least one test case, or the endpoint returns a 400. The pass/fail result is cached on the mapper (last_test_passed, last_tested_at) so the builder UI can show status without re-running the test on every page load.
Taking effect
A mapper runs whenever its parent API is called through the API Gateway, which every published API goes through. If the parent API is already published, adding/editing/deleting a mapper takes effect on the next call — no need to unpublish and republish.
Because that change goes live immediately, changing a mapper on a published API needs a lifecycle_manager, space_admin or admin. An api_developer pulls the API back to draft first, so the change goes through review like any other edit.
transform_spec is syntax-checked when you save, so a typo is rejected with a 422 instead of failing every live call through that operation. If a mapper still fails at call time (for example, a field missing from a real response), the gateway returns 502 request_mapping_failed or response_mapping_failed; the full error is in the server log under the response's reference.
Related
- AI Protocol Mappers — the other half of the pipeline, for wire-format conversion (XML/SOAP/form) rather than JSON reshaping
- API Gateway — the exact order mappers run in on a real request
- Tool Server Publishing — why having an enabled mapper changes which URL gets registered