A scripted REST service defines a REST endpoint in EBX® and its related script. You create and maintain these services in the Script IDE. After you compile and publish the service from the IDE, it becomes available to REST clients.
Each operation in a scripted REST service is implemented as an EBX® Script exported function that runs when the operation is called via an HTTP request. Exported functions return a rest_response value. The core.rest unit provides utilities to construct responses. Requests and responses only use JSON content.
A scripted REST service defines one or more endpoints. Each endpoint has a URL path and one or more HTTP methods. For each method, you assign an Operation id which is interpreted as an OpenAPI operationId . This value identifies the exported EBX® Script function that runs when a client calls the endpoint with that method. The Operation id value must match the name of corresponding exported function exactly.
The URL path can include path parameters (appearing in braces) and query parameters. Parameters are used by the related function as follows:
Parameter types are inferred from the OpenAPI schema. See Schemas, types, and OpenAPI for details.
Each path parameter becomes a function parameter.
The parameter name matches the path parameter name.
The parameter type is inferred from the OpenAPI schema.
The order of path parameters in the function matches their order in the path template.
Query parameters defined for the operation also become function parameters, following the path parameters in the order they are declared in the OpenAPI document.
Examples (names reflect operationId ):
OpenAPI (excerpt):
{
"openapi": "3.0.1",
"paths": {
"/users": {
"get": { "operationId": "getUsers", "responses": { "200": { "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/User" } } } } } } },
"post": { "operationId": "createUser", "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/User" } } } },
"responses": { "201": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/User" } } } } } }
},
"/users/{id}": {
"parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "number" } } ],
"get": { "operationId": "getUser", "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/User" } } } } } },
"delete": { "operationId": "deleteUser", "responses": { "204": {} } }
},
"/display/{p1}/{p2}": {
"parameters": [
{ "name": "p1", "in": "path", "required": true, "schema": { "type": "string" } },
{ "name": "p2", "in": "path", "required": true, "schema": { "type": "number" } }
],
"get": { "operationId": "displayParameters",
"parameters": [
{ "name": "message", "in": "query", "required": true, "schema": { "type": "string" } },
{ "name": "count", "in": "query", "required": false, "schema": { "type": "number" } },
{ "name": "datetime", "in": "query", "required": false, "schema": { "type": "string", "format": "date-time" } },
{ "name": "date", "in": "query", "required": false, "schema": { "type": "string", "format": "date" } },
{ "name": "time", "in": "query", "required": false, "schema": { "type": "string", "format": "time" } }
],
"responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Query" } } } } }
}
}
},
"components": {
"schemas": {
"User": { "type": "object", "properties": { "id": { "type": "number" }, "firstName": { "type": "string" }, "lastName": { "type": "string" } } },
"Query": { "type": "object", "properties": { "p1": { "type": "string" }, "p2": { "type": "number" }, "message": { "type": "string" }, "count": { "type": "number" }, "datetime": { "type": "string", "format": "date-time" }, "date": { "type": "string", "format": "date" }, "time": { "type": "string", "format": "time" } } },
"Error": { "type": "object", "properties": { "message": { "type": "string" } } }
}
}
}
Equivalent exported methods:
uses core.rest as rest; uses core.list as list; export function getUsers(): rest_response begin // Return a list of users return rest.responseOf(200, list.of<User>()); end export function createUser(const user: User): rest_response begin return rest.responseOf(201, user); end export function getUser(id: decimal): rest_response begin // Return typed object or a typed error var err := instanceOf<Error>(); err.message := 'Not found'; return rest.responseOf(404, err); end export function deleteUser(const id: decimal): rest_response begin return rest.emptyResponse(204); end export function displayParameters( p1: string, p2: decimal, message: string, count: decimal, datetime: datetime, date: date, time: time): rest_response begin var q := instanceOf<Query>(); q.p1 := p1; q.p2 := p2; q.message := message; q.count := count; q.datetime := datetime; q.date := date; q.time := time; return rest.responseOf(200, q); end
In the context of a scripted REST service, exported functions are REST operation handlers. Each exported function must return a rest_response and can accept parameters for path, query, or body as defined by the service contract.
Declare path and query parameters using scalar types such as string , int , decimal , boolean , date , time , or datetime .
The request body, when present, is typically declared as a typed object generated from the components.schemas API, or as json_value for unstructured JSON.
The function name must match the OpenAPI operationId .
Parameter order: path parameters first (path order), then query parameters (OpenAPI document order).
For a full example, see the combined OpenAPI + exported methods above.
Scripted REST services handle parameter parsing and validation automatically:
Path and query parameters are parsed according to their declared types.
Invalid numbers and dates return 400 with a standard error payload, for example: "Path parameter 'year' is not valid." or "Query parameter 'date' is not valid." as applicable.
For boolean path parameters, non-"true" values are coerced to false rather than rejected.
Empty path segments are rejected with 400 by the container before reaching the script.
To produce responses from exported functions, use core.rest utilities:
rest.responseOf(status: int, content: any) creates a JSON response with an optional status code.
If status is null, 200 is assumed.
content can be a JSON object type, any json_value , or lists of these.
rest.emptyResponse(status: int) creates a response without a body.
If status is null, 204 is assumed.
OpenAPI 3 describes operations and data shapes. In scripted REST services:
Each components.schemas entry becomes an EBX® Script type with the same name (for example: User , Error , Meeting , Query ). Fields map 1:1 to schema properties.
Scalars map as follows:
integer (format: int32) → int
number → decimal
boolean → boolean
string → string
string (format: date-time) → datetime
string (format: date) → date
string (format: time) → time
Responses/bodies can be typed objects, lists of typed objects, or generic JSON via json_value .
Examples:
// Typed body
export function updateUser(const user: User): rest_response
begin
return rest.responseOf(200, user);
end
// List of strings
uses core.list as list;
uses core.json as json;
export function getTags(): rest_response
begin
var tags := list.of('one', 'two');
return rest.responseOf(200, json.copyOfStrings(tags));
end
When using OpenAPI-driven templates, object types and field names are derived from the component schemas. Keep the returned structure aligned with the declared types for clear compile-time validation.
Scripted REST services support a subset of OpenAPI 3.0 features:
Paths: keys are validated as path templates. Keep them free of any base root (the script name defines the base path).
Methods: GET , POST , PUT , DELETE supported. Response keys must be integer HTTP codes in a valid range.
Security: security: [] allows anonymous access. Any other security object is not supported.
Parameters:
At path level: only in: path are processed; names must exist in the path template.
At method level: only in: query are processed; path must be declared at path level.
Each parameter must declare a schema with a supported primitive type ( string , integer , number , boolean ) and optional format ( date , time , date-time ). Parameters cannot be arrays or objects and cannot use $ref .
Request bodies: only application/json is considered. The schema may be a $ref to a component schema or an inline scalar/array/object as applicable.
Responses: only application/json is considered. Each status can optionally declare a JSON schema (scalar, array, or $ref ).
Components schemas: only object types are supported with properties; additionalProperties must be absent or false. $ref targets must be under #/components/schemas .
Configure scripted REST services to allow anonymous access or require authentication. Implement your handlers so they do not assume a user context unless required by the endpoint design, and return appropriate status codes when access should be restricted.
At runtime, _ebx.anonymous indicates whether the request is anonymous.
For anonymous-friendly endpoints, the OpenAPI operation typically sets security: [] .
Use _ebx.session.userId when a user session exists.
Example:
uses core.rest as rest;
uses core.list as list;
uses core.json as json;
export function getSessionDetails(): rest_response
begin
var userId := '';
if _ebx.anonymous then
userId := 'anonymous';
else
userId := _ebx.session.userId;
var content := list.of(json.ofString(userId));
return rest.responseOf(200, content);
end
Use regular EBX® Script transaction and data APIs to read and modify EBX® data. The following example includes a typical pattern with a transaction block and a ref to carry the response across the block:
uses core.rest as rest;
uses core.data as db;
uses core.complex as cp;
// Model reference for dataset types used in generics below (replace with your own module/name)
references model "directory" from module "my-module" as dir;
export function updateUser(const user: User): rest_response
begin
ref response: rest_response;
execute transaction tr on db.findDataspace('rest-test') as system
begin
const directory := db.findDataset(tr.dataspace, 'directory');
const table := db.findTable<dir.User>(directory);
var pk := cp.primaryKeyOf<dir.User>();
pk.id := user.id;
var record := db.lookupRecordForUpdate(tr, table, pk);
if isNull(record) then
begin
var error := instanceOf<Error>();
error.message := 'User "' | user.id | '" not found';
response := rest.responseOf(404, error);
end
else
begin
record.firstName := user.firstName;
record.lastName := user.lastName;
db.saveRecord(record);
response := rest.responseOf(200, user);
end
end
return response;
end
When the request should run with elevated privileges, use execute transaction … as system . Otherwise, omit as system to use the authenticated user context.
Scripted REST services handle errors as follows:
Invalid path or query parameters are rejected automatically with 400 and a standard error payload.
For business errors (not found, already exists, validation), return typed error content with an appropriate status code (for example, 404 , 400 ).
Example:
var err := instanceOf<Error>(); err.message := 'User not found'; return rest.responseOf(404, err);
REST service (calling external services from DSL)
Reference (language reference and types)
REST Service tutorial (detailed walkthrough of creating a scripted REST service)