Client Guide (Java)
This guide shows how to call the Ambience or Repertoire Server endpoints directly from Java using standard HTTP libraries. No external client jar is needed — just pick any HTTP client and follow the protocol below.
Overview
The server exposes three REST endpoints:
| Operation | Endpoint | Description |
|---|---|---|
| Generate Data | /ds-engine/api/v1/generate |
Run a DataSource file and stream the result |
| Render Report | /rml-engine/api/v1/render |
Render an RML report to PDF, XLSX, etc. |
| Render DocX | /docx-engine/api/v1/render |
Render a DocX template to DOCX or PDF |
All endpoints accept a POST with a JSON body and Content-Type: application/json. The response body is the generated output (data, PDF, DOCX, etc.) on HTTP 200.
The Ambience server listens on port 1740 by default. The Repertoire server listens on port 1730 by default. Both of these are configurable, as is the protocol http: or https: See the server setup for details about your configuration.
Table of Contents
- Authentication
- API Token (Recommended)
- Username and Password
- Generate Data
- Render Report
- Render DocX
- Response Headers
- Status Codes
- Complete Java Example
- Appendix: MIME Types
Authentication
Two authentication methods are supported. API token is recommended since it is stateless — no login step, no cookie management.
API Token (Recommended)
API Tokens can be generated within the Ambience/Repertoire server
Append elx.token as a query parameter to every request URL:
POST https://your-server:your-port/ds-engine/api/v1/generate?elx.token=YOUR_TOKEN
If the URL already has query parameters, append with &:
POST https://your-server:your-port/ds-engine/api/v1/generate?someParam=value&elx.token=YOUR_TOKEN
Username and Password
IMPORTANT: You can only use username and password login if you enable the feature in your server config:
elixir.sso.server.resource-owner-password-enabled = true
A two-step process: login first to get a session cookie, then use that cookie on every subsequent request. The cookie is named elx-amb for Ambience and elx-rep for Repertoire. We will use elx-amb in the examples.
Step 1: Login
POST {base}/remote-access/login
Content-Type: application/x-www-form-urlencoded
username=admin&password=secret&totp=
The totp field is optional. Omit it or send empty string if two-factor authentication is not enabled. Note that the totp value is time-sensitive.
A successful response (HTTP 200) returns JSON:
{
"id": "user-123",
"username": "admin",
"email": "admin@example.com",
"roles": ["admin"],
"privileges": ["*"],
"cookieName": "A2021",
"cookieValue": "abc123def456"
}
Step 2: Use the cookie
Send the cookie on every subsequent request:
Cookie: elx-amb=abc123def456
Replace elx-amb with elx-rep if necessary, and the value with what the server returned. If the login fails, the server returns HTTP 401.
Generate Data
Runs a DataSource (.ds) file and returns the generated data in the response body.
Request:
POST {base}/ds-engine/api/v1/generate
Content-Type: application/json
JSON body:
{
"ds": "/path/to/your/file.ds",
"paramName1": "value1",
"paramName2": "value2"
}
ds(required): File path on the server- Additional keys are parameters passed to the DataSource
Response:
- HTTP 200: Response body is the generated data
- Non-200: Request failed; check response headers for details
Render Report
Renders an RML report template to a target format.
Request:
POST {base}/rml-engine/api/v1/render
Content-Type: application/json
JSON body:
{
"rml": "/path/to/your/report.rml",
"mimeType": "application/pdf",
"paramName1": "value1",
"renderDetails": {
"sheetName": "Data"
}
}
rml(required): Report file path on the servermimeType(required): Target output format (see MIME Types)- Additional keys (besides
rml,mimeType,renderDetails) are report parameters renderDetails(optional): Format-specific render options as a nested JSON object
Response:
- HTTP 200: Response body is the rendered file (PDF, XLSX, etc.)
- Non-200: Request failed; check response headers for details
Render DocX
Renders a DocX template with data binding.
Request:
POST {base}/docx-engine/api/v1/render
Content-Type: application/json
JSON body:
{
"docx": "/path/to/your/template.docx",
"mimeType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"city": "Cliffside"
}
docx(required): Template file path on the servermimeType(required): Target output format- Additional keys are template parameters
Response:
- HTTP 200: Response body is the rendered file
- Non-200: Request failed; check response headers for details
Response Headers
Every response includes metadata headers prefixed with X-elxjob. The common ones are:
| Header | Type | Description |
|---|---|---|
X-elxjob.StatusCode |
Integer | Job status (see Status Codes) |
X-elxjob.ElapsedTime |
Long | Elapsed time in milliseconds |
X-elxjob.ByteCount |
Long | Size of the response body in bytes |
X-elxjob.RecordCount |
Integer | Number of records (data generation) |
X-elxjob.PageCount |
Integer | Number of pages rendered |
X-elxjob.MimeType |
String | Output MIME type |
Status Codes
The X-elxjob.StatusCode header is only emitted by rml-engine (not docx-engine). It indicates the job outcome:
| Code | Meaning |
|---|---|
| 1 | Completed successfully |
| 3 | Render timed out |
| 4 | Exception during render (see X-elxjob.Exception header for details) |
When a job fails before or during queuing (e.g. cancellation), rml-engine returns HTTP 4xx with no job headers.
Complete Java Example
A self-contained Java class using java.net.http.HttpClient (Java 11+):
import java.io.ByteArrayOutputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Map;
import java.util.Optional;
public class RepertoireClientExample {
private final HttpClient httpClient;
private final String baseUrl;
private final Auth auth;
// --- Authentication ---
public enum AuthType { TOKEN, USER_PASSWORD }
public interface Auth {
HttpRequest apply(HttpRequest.Builder builder);
}
public record TokenAuth(String token) implements Auth {
@Override
public HttpRequest apply(HttpRequest.Builder builder) {
return builder.build(); // token added in URL
}
}
public record CookieAuth(String cookieName, String cookieValue) implements Auth {
@Override
public HttpRequest apply(HttpRequest.Builder builder) {
return builder.header("Cookie", cookieName + "=" + cookieValue).build();
}
}
// --- Constructors ---
/** Create client with API token authentication. */
public RepertoireClientExample(String baseUrl, String apiToken) {
this.httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
this.baseUrl = baseUrl.replaceAll("/*$", "");
this.auth = new TokenAuth(apiToken);
}
/** Create client with username/password authentication. */
public RepertoireClientExample(String baseUrl, String username, String password) {
this(baseUrl, username, password, "");
}
/** Create client with username/password and optional TOTP. */
public RepertoireClientExample(String baseUrl, String username, String password, String totp) {
this.httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
this.baseUrl = baseUrl.replaceAll("/*$", "");
this.auth = login(username, password, totp);
}
private CookieAuth login(String username, String password, String totp) {
String body = "username=" + urlEncode(username)
+ "&password=" + urlEncode(password)
+ "&totp=" + urlEncode(totp);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + "/remote-access/login"))
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = send(request);
if (response.statusCode() != 200) {
throw new RuntimeException("Access Denied (HTTP " + response.statusCode() + ")");
}
LoginResult login = parseLogin(response.body());
return new CookieAuth(login.cookieName, login.cookieValue);
}
// --- Operations ---
/** Generate data from a DataSource file. */
public JobResult generateData(String dsPath, Map<String, String> params) {
Map<String, String> body = new java.util.HashMap<>(params);
body.put("ds", dsPath);
return postJson(dsEndpoint(), body);
}
/** Render an RML report. */
public JobResult renderReport(String rmlPath, String mimeType,
Map<String, String> params,
Map<String, String> renderDetails) {
Map<String, String> body = new java.util.HashMap<>(params);
body.put("rml", rmlPath);
body.put("mimeType", mimeType);
if (renderDetails != null && !renderDetails.isEmpty()) {
body.put("renderDetails", toJson(renderDetails));
}
return postJson(renderEndpoint(), body);
}
/** Render a DocX template. */
public JobResult renderDocX(String docxPath, String mimeType,
Map<String, String> params) {
Map<String, String> body = new java.util.HashMap<>(params);
body.put("docx", docxPath);
body.put("mimeType", mimeType);
return postJson(docxEndpoint(), body);
}
// --- Internal ---
private String dsEndpoint() {
return tokenUrl(baseUrl + "/ds-engine/api/v1/generate");
}
private String renderEndpoint() {
return tokenUrl(baseUrl + "/rml-engine/api/v1/render");
}
private String docxEndpoint() {
return tokenUrl(baseUrl + "/docx-engine/api/v1/render");
}
private String tokenUrl(String url) {
if (auth instanceof TokenAuth t) {
String sep = url.contains("?") ? "&" : "?";
return url + sep + "elx.token=" + urlEncode(t.token());
}
return url;
}
private JobResult postJson(String url, Map<String, String> body) {
return sendJob(HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(
toJson(body), StandardCharsets.UTF_8))
.timeout(Duration.ofMinutes(5)));
}
private HttpResponse<String> send(HttpRequest request) {
try {
return httpClient.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private JobResult sendJob(HttpRequest.Builder builder) {
// Apply auth headers
if (auth instanceof CookieAuth c) {
builder.header("Cookie", c.cookieName() + "=" + c.cookieValue());
}
HttpRequest request = builder.build();
try {
HttpResponse<byte[]> response = httpClient.send(
request, HttpResponse.BodyHandlers.ofByteArray());
return new JobResult(
response.body(),
response.statusCode(),
extractJobHeaders(response.headers().map()),
response.body().length
);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// --- Job Result ---
public static class JobResult {
public final byte[] body;
public final int httpStatusCode;
public final int jobStatusCode;
public final long byteSize;
public final Map<String, String> headers;
JobResult(byte[] body, int httpStatusCode, Map<String, String> headers, long byteSize) {
this.body = body;
this.httpStatusCode = httpStatusCode;
this.headers = headers;
this.byteSize = byteSize;
this.jobStatusCode = parseIntHeader(headers.get("StatusCode"), -1);
}
public String getString(String name) {
return headers.getOrDefault(name, "");
}
public long getLong(String name) {
return Long.parseLong(headers.getOrDefault(name, "0"));
}
public int getInt(String name) {
return parseIntHeader(headers.get(name), -1);
}
private static int parseIntHeader(String value, int defaultValue) {
if (value == null) return defaultValue;
try { return Integer.parseInt(value); }
catch (NumberFormatException e) { return defaultValue; }
}
@Override
public String toString() {
return "JobResult{http=" + httpStatusCode + ", status=" + jobStatusCode
+ ", bytes=" + byteSize + ", headers=" + headers + "}";
}
}
// --- JSON helpers (replace with your preferred library) ---
private static String toJson(Map<String, String> map) {
StringBuilder sb = new StringBuilder("{");
boolean first = true;
for (Map.Entry<String, String> e : map.entrySet()) {
if (!first) sb.append(",");
sb.append("\"").append(escapeJson(e.getKey())).append("\":\"")
.append(escapeJson(e.getValue())).append("\"");
first = false;
}
sb.append("}");
return sb.toString();
}
private static String escapeJson(String s) {
return s.replace("\\", "\\\\")
.replace("\"", "\\\"")
.replace("\n", "\\n")
.replace("\r", "\\r")
.replace("\t", "\\t");
}
// --- Login parsing (replace with your preferred JSON library) ---
private record LoginResult(String cookieName, String cookieValue) {}
private static LoginResult parseLogin(String json) {
String cookieName = extractJsonString(json, "cookieName");
String cookieValue = extractJsonString(json, "cookieValue");
return new LoginResult(cookieName, cookieValue);
}
private static String extractJsonString(String json, String key) {
String pattern = "\"" + key + "\":\"";
int start = json.indexOf(pattern);
if (start == -1) throw new RuntimeException("Key not found: " + key);
start += pattern.length();
int end = json.indexOf("\"", start);
if (end == -1) throw new RuntimeException("Cannot parse value for: " + key);
return json.substring(start, end);
}
private static Map<String, String> extractJobHeaders(Map<String, List<String>> headerMap) {
Map<String, String> result = new java.util.HashMap<>();
for (Map.Entry<String, List<String>> e : headerMap.entrySet()) {
if (e.getKey().startsWith("X-elxjob.")) {
String name = e.getKey().substring("X-elxjob.".length());
result.put(name, e.getValue().get(0));
}
}
return result;
}
private static String urlEncode(String s) {
try { return java.net.URLEncoder.encode(s, StandardCharsets.UTF_8.name()); }
catch (Exception e) { throw new RuntimeException(e); }
}
// --- Demo ---
public static void main(String[] args) throws Exception {
// Option 1: API token auth
// var client = new RepertoireClientExample("https://your-server", "your-token-here");
// Option 2: Username/password auth (you should definitely change the default password if your server is visible to others)
var client = new RepertoireClientExample("https://your-server", "admin", "sa");
// Generate data
var dataResult = client.generateData(
"/ElixirSamples/DataSource/FruitSales.ds",
Map.of()
);
System.out.println("Data: " + new String(dataResult.body, StandardCharsets.UTF_8));
System.out.println(dataResult);
// Render report to PDF
var reportResult = client.renderReport(
"/ElixirSamples/Report/RML/Master-Detail Report.rml",
"application/pdf",
Map.of("Staff Name", "John Doe"), // report parameters
Map.of() // render details
);
java.nio.file.Files.write(
java.nio.file.Path.of(System.getProperty("java.io.tmpdir"), "report.pdf"),
reportResult.body
);
System.out.println(reportResult);
// Render DocX
var docxResult = client.renderDocX(
"/ElixirSamples/Report/DocX/Customer Profile by Location.docx",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
Map.of("city", "Cliffside")
);
java.nio.file.Files.write(
java.nio.file.Path.of(System.getProperty("java.io.tmpdir"), "output.docx"),
docxResult.body
);
System.out.println(docxResult);
}
}
Note: The JSON helpers in the example above are minimal to provide a self-contained example. In production, we recommend you use a JSON library (Jackson, Gson, org.json, etc.) - you probably already have a favourite - instead of the hand-rolled
toJsonandparseLoginmethods.
Appendix: MIME Types
Common output MIME types (the exact choices are determined by server side configuration of what pipelines to enable):
| Format | MIME Type |
|---|---|
application/pdf |
|
| DOCX | application/vnd.openxmlformats-officedocument.wordprocessingml.document |
| XLSX | application/vnd.openxmlformats-officedocument.spreadsheetml.sheet |
| CSV | text/csv |
| TXT | text/plain |