> ## Documentation Index
> Fetch the complete documentation index at: https://docs.jojapi.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Target Transformations (classic gateway)

> Request and Response Transformations at the classic API Gateway

<Warning>
  This page describes the template expressions of the **classic gateway**. APIs served by the **edge gateway** (an *Edge gateway* badge on the Code tab) use the [template](/studio/api-targeting) with placeholders and variables instead, and [Worker code](/studio/custom-code) for anything the template cannot express. When an API is moved to the edge gateway, its expressions are converted automatically.
</Warning>

When you configure an API Target, the gateway acts as a proxy between your API consumers and your backend server. By default, requests are forwarded as-is to your target server. However, you often need to modify requests before they reach your backend or transform responses before they're returned to consumers.

Target Transformations allow you to:

* **Rewrite paths** - Map consumer-facing URLs to your internal API structure
* **Inject headers** - Add authentication tokens, API keys, or tracking headers
* **Modify query parameters** - Add, remove, or transform URL parameters
* **Transform request bodies** - Restructure payloads, add metadata, or wrap content
* **Transform responses** - Filter fields, restructure data, or normalize formats
* **Calculate dynamic credits** - Charge based on actual usage from response data

All transformations use an [Expression Language](https://symfony.com/doc/current/components/expression_language.html) that lets you access request data, user context, and response data dynamically.

## Expression Syntax

Expressions are enclosed in double curly braces:

```
{{ expression }}
```

Everything outside `{{ }}` is treated as static text. You can mix static content with multiple expressions:

```
/api/v2{{ request.path }}?user={{ context.userId }}
```

## Where Expressions Can Be Used

Expressions are supported in the following Target configuration fields:

| Field | Tab | Description |
| - | - | - |
| Path Transformation | Request | Rewrite the URL path sent to your backend |
| Custom Headers | Request | Add or modify headers sent to your backend |
| Query Parameters | Request | Add or modify URL query parameters |
| Request Body | Request | Transform the request payload |
| Custom Response Headers | Response | Add or modify headers returned to consumer |
| Response Body | Response | Transform the response payload |
| Dynamic Credits | Response | Calculate credits based on response data |

## Context Variables

Different variables are available depending on where the expression is used.

### Request Variables

Available in: Path, Headers, Query, Request Body

| Variable | Type | Description |
| - | - | - |
| `request.method` | string | HTTP method (GET, POST, etc.) |
| `request.scheme` | string | Protocol (http or https) |
| `request.host` | string | Request hostname |
| `request.port` | int | Server port |
| `request.path` | string | Request path (after API slug) |
| `request.url` | string | Full request URL |
| `request.ip` | string | Client IP address |
| `request.country` | string | Client country code |
| `request.body` | string | Raw request body |
| `request.json` | object | Parsed JSON body (if valid JSON) |
| `request.headers` | object | Request headers (single value per key) |
| `request.headersAll` | object | Request headers (array of values per key) |
| `request.query` | object | Query parameters (single value per key) |
| `request.queryAll` | object | Query parameters (array of values per key) |
| `request.raw.path` | string | Raw path before transformation |
| `request.raw.query` | string | Raw query string |

### Context Variables

Available in: All expressions

| Variable | Type | Description |
| - | - | - |
| `context.userId` | string | Client user identifier |
| `context.username` | string | Client username (users can change it) |
| `context.planId` | string | Client's subscription plan slug |
| `context.planType` | string | Plan type (`periodic` or `payasyougo`) |
| `context.endpointCredits` | int\|null | Credits defined for this endpoint (`null` for endpoints with flexible credits) |

### Response Variables

Available in: Response Body, Response Headers, Dynamic Credits

| Variable | Type | Description |
| - | - | - |
| `response.status` | int | HTTP status code from target |
| `response.body` | string | Raw response body |
| `response.json` | object | Parsed JSON response (if valid JSON) |
| `response.headers` | object | Response headers (single value per key) |
| `response.headersAll` | object | Response headers (array of values per key) |
| `response.latency` | float | Request latency in seconds |

### Rendered Request Variables

Available in: Response Body, Response Headers, Dynamic Credits

The `renderedRequest` object contains the transformed request that was actually sent to the target server.

| Variable | Type | Description |
| - | - | - |
| `renderedRequest.method` | string | Final HTTP method sent |
| `renderedRequest.url` | string | Final URL sent to target |
| `renderedRequest.host` | string | Target hostname |
| `renderedRequest.path` | string | Transformed path |
| `renderedRequest.body` | string | Transformed request body |
| `renderedRequest.json` | object | Parsed transformed body |
| `renderedRequest.headers` | object | Headers sent to target |
| `renderedRequest.headersAll` | object | Headers sent (array per key) |

## Accessing Properties

Use dot notation for object properties:

```
{{ request.json }}
{{ response.headers }}
```

Use bracket notation for dynamic keys or special characters:

```
{{ request.headers["x-custom-header"] }}
{{ response.json["data"][0] }}
```

## Functions

### String Functions

| Function | Description | Example |
| - | - | - |
| `lower(str)` | Convert to lowercase | `{{ lower(request.headers["accept"]) }}` |
| `upper(str)` | Convert to uppercase | `{{ upper(request.method) }}` |
| `trim(str)` | Remove leading/trailing whitespace | `{{ trim(request.query["name"]) }}` |
| `concat(a, b)` | Concatenate two strings | `{{ concat("/v2", request.path) }}` |
| `substring(str, start, len?)` | Extract substring | `{{ substring(request.path, 0, 10) }}` |
| `replace(str, from, to)` | Replace occurrences | `{{ replace(request.path, "/v1", "/v2") }}` |
| `split(str, delim)` | Split into array | `{{ split(request.query["ids"], ",") }}` |
| `join(arr, delim)` | Join array elements | `{{ join(request.json["tags"], ",") }}` |
| `startsWith(str, prefix)` | Check prefix | `{{ startsWith(request.path, "/api") }}` |
| `endsWith(str, suffix)` | Check suffix | `{{ endsWith(request.path, ".json") }}` |
| `contains(str, needle)` | Check if contains | `{{ contains(request.body, "error") }}` |
| `padLeft(str, len, char?)` | Pad start | `{{ padLeft(request.query["id"], 10, "0") }}` |
| `padRight(str, len, char?)` | Pad end | `{{ padRight(request.query["code"], 5) }}` |

### Array Functions

| Function | Description | Example |
| - | - | - |
| `first(arr)` | Get first element | `{{ first(response.json["results"]) }}` |
| `last(arr)` | Get last element | `{{ last(response.json["items"]) }}` |
| `slice(arr, start, len?)` | Extract portion | `{{ slice(response.json["data"], 0, 5) }}` |
| `keys(obj)` | Get object keys | `{{ keys(request.headers) }}` |
| `values(obj)` | Get object values | `{{ values(request.query) }}` |
| `in(needle, arr)` | Check membership | `{{ in(request.method, ["GET", "POST"]) }}` |
| `merge(arr1, arr2)` | Merge arrays | `{{ merge(arr1, arr2) }}` |
| `len(arr)` | Get length | `{{ len(response.json["results"]) }}` |

### Encoding Functions

| Function | Description | Example |
| - | - | - |
| `urlEncode(str)` | URL encode | `{{ urlEncode(request.query["callback"] }}` |
| `base64Encode(str)` | Base64 encode | `{{ base64Encode(request.body) }}` |
| `base64Decode(str)` | Base64 decode | `{{ base64Decode(request.headers["auth"]) }}` |
| `jsonEncode(val)` | JSON encode | `{{ jsonEncode(request.json["data"]) }}` |
| `jsonDecode(str)` | JSON decode | `{{ jsonDecode(request.body) }}` |

### Type Functions

| Function | Description | Example |
| - | - | - |
| `toInt(val)` | Convert to integer | `{{ toInt(request.query["page"]) }}` |
| `toFloat(val)` | Convert to float | `{{ toFloat(request.query["price"]) }}` |
| `isString(val)` | Check if string | `{{ isString(request.body) }}` |
| `isNumber(val)` | Check if number | `{{ isNumber(request.json["count"]) }}` |
| `isArray(val)` | Check if array | `{{ isArray(response.json["items"]) }}` |
| `isBool(val)` | Check if boolean | `{{ isBool(request.json["active"]) }}` |

### Utility Functions

| Function | Description | Example |
| - | - | - |
| `empty(val)` | Check if null/empty | `{{ empty(request.query["filter"]) }}` |
| `exists(val)` | Check if not null | `{{ exists(request.json["user"]) }}` |
| `default(val, fallback)` | Default if empty | `{{ default(request.query["page"], "1") }}` |
| `coalesce(a, b, ...)` | First non-empty | `{{ coalesce(request.json["id"], request.query["id"] }}` |
| `min(a, b)` | Minimum value | `{{ min(request.json["count"], 100) }}` |
| `max(a, b)` | Maximum value | `{{ max(request.json["page"], 1) }}` |
| `abs(val)` | Absolute value | `{{ abs(request.json["offset"]) }}` |
| `round(val, precision?)` | Round number | `{{ round(response.json["price"], 2) }}` |
| `floor(val)` | Round down | `{{ floor(response.json["rating"]) }}` |
| `ceil(val)` | Round up | `{{ ceil(response.json["pages"]) }}` |
| `buildQuery(params)` | Build URL query string | `{{ buildQuery({"page":1,"limit":10}) }}` |

### Hash & Random Functions

| Function | Description | Example |
| - | - | - |
| `hash(algo, str)` | Hash string | `{{ hash("sha256", request.body) }}` |
| `uuid()` | Generate UUID v4 | `{{ uuid() }}` |
| `random(min?, max?)` | Random integer | `{{ random(1, 100) }}` |

Supported hash algorithms: `md5`, `sha1`, `sha256`, `sha512`

### Date Functions

| Function | Description | Example |
| - | - | - |
| `now()` | Current Unix timestamp | `{{ now() }}` |
| `date(format, timestamp?)` | Format date | `{{ date("Y-m-d H:i:s") }}` |

### URL Functions

| Function | Description | Example |
| - | - | - |
| `parseUrl(url, component)` | Parse URL part | `{{ parseUrl(request.url, 1) }}` |

Component values: 1=scheme, 2=host, 3=port, 5=path, 6=query, 7=fragment

## Operators

### Comparison

```
{{ request.json["count"] > 10 }}
{{ request.method == "POST" }}
{{ response.status != 200 }}
{{ request.json["price"] >= 100 }}
```

### Logical

```
{{ request.json["active"] && request.json["verified"] }}
{{ request.query["format"] == "xml" || request.query["format"] == "json" }}
{{ !empty(request.json["email"]) }}
```

### Ternary

```
{{ request.json["premium"] ? "pro" : "free" }}
{{ empty(request.query["limit"]) ? 10 : toInt(request.query["limit"]) }}
```

### String Concatenation

```
{{ "/api/v2" ~ request.path }}
{{ request.json.["first"] ~ " " ~ request.json["last"] }}
```

### Arithmetic

```
{{ request.json["price"] * 1.1 }}
{{ response.json["total"] / response.json["count"] }}
{{ request.json["page"] + 1 }}
```

## Usage Examples

### Path Transformation

Prefix all requests with a version:

```
/v2{{ request.path }}
```

Dynamic path based on request:

```
/users/{{ request.json["user_id"] }}/profile
```

### Custom Headers

Add authentication header:

```
Bearer {{ base64Encode(context.userId ~ ":" ~ context.planId) }}
```

Forward user context:

```
{{ context.userId }}
```

### Query Parameters

Default pagination:

```
{{ default(request.query["limit"], "20") }}
```

### Request Body Transformation

Wrap original body:

```json theme={null}
{
  "data": {{ request.body }},
  "metadata": {
    "user": "{{ context.userId }}",
    "timestamp": {{ now() }}
  }
}
```

### Response Body Transformation

Extract specific fields:

```json theme={null}
{
  "items": {{ jsonEncode(response.json["data"]["results"]) }},
  "total": {{ response.json["data"]["total_count"] }}
}
```

### Dynamic Credits

Use credits from response header:

```
{{ response.headers["x-credits-used"] }}
```

Calculate credits based on response:

```
{{ len(response.json["results"]) }}
```

Fixed credits on success, zero on error:

```
{{ response.status >= 200 && response.status < 300 ? context.endpointCredits : 0 }}
```

## Limits

* Maximum template size: 200,000 characters
* Maximum expressions per template: 100

## Notes

* Expressions that fail to evaluate will throw an error and the request will return a gateway error
* JSON objects and arrays in expression results are automatically serialized
* The `request.json` and `response.json` variables are `null` if the body is not valid JSON
* Header names are case-insensitive and normalized to lowercase


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.