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.
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 that lets you access request data, user context, and response data dynamically.
Expression Syntax
Expressions are enclosed in double curly braces:
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
Prefix all requests with a version:
Dynamic path based on request:
/users/{{ request.json["user_id"] }}/profile
Add authentication header:
Bearer {{ base64Encode(context.userId ~ ":" ~ context.planId) }}
Forward user context:
Query Parameters
Default pagination:
{{ default(request.query["limit"], "20") }}
Request Body Transformation
Wrap original body:
{
"data": {{ request.body }},
"metadata": {
"user": "{{ context.userId }}",
"timestamp": {{ now() }}
}
}
Response Body Transformation
Extract specific fields:
{
"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