REST Bindings
Overview
A REST binding publishes a SpEL script as an HTTP endpoint, allowing external systems to invoke tSM logic with a simple POST request. The endpoint is available under the API base path:
POST /api/v2/scripts/<urlPath>
No additional coding, controller registration, or deployment is required. Define the binding row and the endpoint is live.
How it works
- Routing — the platform matches the request path to a binding row by
config.urlPath. - Authentication — the request must carry a valid
Authorizationheader (see Authentication & authorization below). - Validation — if the script defines a
paramsFormCode, the request body is validated against its JSON Schema before execution. - Execution — the script runs with the request body fields and any path variables injected as context variables.
- Response — the script's return value is serialized as JSON and sent back to the caller.
Register-row schema (Scripts.Bindings.RestInvocations)
Bindings are configured in the register "Scripts / Bindings / REST Invocations" (internal code Scripts.Bindings.RestInvocations).
Actual register value format:
{
"config": {
"script": "Bubak.Binding.Customer",
"urlPath": "/customer/{customerKey}"
}
}
| Field | Type | Required | Description |
|---|---|---|---|
config.script | string | ✔ | Code of the target script in the Scripts register. |
config.urlPath | string | ✔ | URL path suffix under /api/v2/scripts (supports {placeholder} segments). |
Path variables
The config.urlPath field supports path variable placeholders using curly braces. Each placeholder becomes a named context variable (#variable) inside the script, populated from the corresponding URL segment.
Syntax
/customer/{customerKey}
The effective endpoint is <baseUrl>/api/v2/scripts + config.urlPath:
config.urlPath | Endpoint URL |
|---|---|
/demo/hello | /api/v2/scripts/demo/hello |
/customers/get/{customerId} | /api/v2/scripts/customers/get/{customerId} |
/orders/{orderId}/items/{itemId} | /api/v2/scripts/orders/{orderId}/items/{itemId} |
How variables are injected
Path variables are injected as string context variables in the script, alongside the request body fields:
| Source | Variable | Example value |
|---|---|---|
URL segment {customerId} | #customerId | "12345" |
URL segment {orderId} | #orderId | "ORD-001" |
JSON body key name | #name | "Acme Corp" |
If a body key and a path variable have the same name, the path variable wins.
Example with path variables
Binding row
{
"config": {
"script": "Customer.GetById",
"urlPath": "/customers/get/{customerId}"
}
}
Script — Customer.GetById
@customer.customer.getByCode(#customerId)
curl
curl -X POST https://tsm.example.com/api/v2/scripts/customers/get/12345 \
-H 'Authorization: Bearer <token>'
The platform extracts 12345 from the URL and injects it as #customerId.
Example with multiple path variables and a body
Binding row
{
"config": {
"script": "Order.UpdateItem",
"urlPath": "/orders/{orderId}/items/{itemId}/update"
}
}
curl
curl -X POST https://tsm.example.com/api/v2/scripts/orders/ORD-001/items/42/update \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{ "quantity": 5 }'
Inside the script: #orderId = "ORD-001", #itemId = "42", #quantity = 5.
Request / Response
| Direction | Format |
|---|---|
| Request | JSON body → injected as named variables into the script context (each top-level key becomes #key). Path variables are injected the same way. |
| Response | The script's return value serialized as JSON. |
Role of params and result schema
When the target script defines paramsFormCode and/or resultFormCode, the REST binding benefits from the same typed-contract mechanism used across all binding types:
| Script field | Effect on REST binding |
|---|---|
paramsFormCode | The JSON Schema validates the request body before the script executes. Invalid payloads are rejected with a 400 Bad Request and a descriptive error. |
resultFormCode | Documents the response structure. External consumers can read the schema to understand what the endpoint returns (no runtime enforcement yet). |
Because a tSM Form combines JSON Schema and UI definition in a single artefact:
- Validation — the request body is checked against the schema; type, format, and required-field constraints are enforced automatically.
- Documentation — field descriptions, labels, and constraints serve as a machine-readable API contract for external consumers.
Publishing a script without paramsFormCode still works — the body is passed to the script as-is without validation. Adding a form is recommended for any endpoint called by third-party integrations.
Example
Script — Demo.Hello (code: Demo.Hello)
"Hello, " + (#name ?: "world") + "!"
Binding row
{
"config": {
"script": "Demo.Hello",
"urlPath": "/demo/hello"
}
}
curl
curl -X POST https://tsm.example.com/api/v2/scripts/demo/hello \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{ "name": "world" }'
Response
"Hello, world!"
Calling REST bindings from external systems
REST bindings are standard HTTP endpoints — any HTTP client can call them. Below are examples for common scenarios.
From another application (curl / HTTP client)
curl -X POST https://tsm.example.com/api/v2/scripts/customers/get/12345 \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-H 'X-Tsm-Tenant: default' \
-d '{ "includeHistory": true }'
From a frontend (JavaScript / fetch)
const response = await fetch('https://tsm.example.com/api/v2/scripts/customers/get/12345', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
'X-Tsm-Tenant': 'default'
},
body: JSON.stringify({ includeHistory: true })
});
const result = await response.json();
From Postman
- Set method to POST and URL to
https://tsm.example.com/api/v2/scripts/<urlPath>. - In the Authorization tab, choose Bearer Token and paste your token.
- In Headers, add
X-Tsm-Tenant: default(if multi-tenant). - In Body, select raw / JSON and enter the request payload.
From another SpEL script
You can also call a REST-bound script from inside another SpEL script using @tsmRestClient, but for internal script-to-script calls it is more efficient to use Script-to-Script Bindings instead.
Authentication & authorization
tSM REST binding endpoints are authenticated — every request must carry credentials.
Bearer token (recommended)
Pass a tSM JWT / OAuth 2 access token in the Authorization header:
Authorization: Bearer <access-token>
This is the recommended and default authentication method. The token identifies the user; all execPrivilege and role-based access rules are evaluated against this user's session.
How to obtain a token depends on your tSM deployment:
| Method | Use-case |
|---|---|
| OAuth 2 client credentials | For service accounts, CI/CD pipelines, and server-to-server integrations. Request a token from the tSM identity provider using client_credentials grant. |
| OAuth 2 authorization code | For user-facing applications where the end-user authenticates interactively. |
| Personal access token | If your tSM deployment supports PATs, generate one in the user profile and use it as the Bearer value. |
Basic authentication (restricted)
Authorization: Basic <base64(username:password)>
Basic authentication is disabled by default. It is only available if the tSM backend has been explicitly configured to allow it. Use Basic auth only for local development or legacy integrations that cannot handle Bearer tokens. Never use Basic auth in production without TLS.
Authorization rules
Once authenticated, the standard tSM authorization model applies:
execPrivilegeon the script — if set, the user's roles must include this privilege.- Role-based access — entity-level and field-level permissions are enforced during script execution, just like any other API call.
- Tenant header — in multi-tenant deployments, the
X-Tsm-Tenantheader must be present.
Error handling
| HTTP status | Condition |
|---|---|
400 | Request body fails paramsFormCode validation. |
401 | Missing or invalid authorization credentials. |
403 | Token lacks the script's execPrivilege. |
404 | No binding row matches the path. |
500 | Script execution error (exception details in the body). |
Transaction behavior
Each REST request runs in its own database transaction; there is no shared state between calls.
- If the script modifies entities, those changes are committed when the request completes successfully.
- If the script throws an exception, the transaction is rolled back.
- External calls made during the script (REST, SOAP, Kafka) are not part of the transaction — see Transactions overview for strategies.
For a full discussion of how transactions interact with SpEL scripts, see SpEL and Transactions.
Performance notes
- REST binding lookup is cached the same way as script-to-script bindings.
- Each request runs in its own transaction; there is no shared state between calls.
Good practices
- Define
paramsFormCode— a parameter form provides automatic validation and serves as living API documentation for external consumers. - Define
resultFormCode— documents the response shape so callers know what to expect without reading the script source. - Use path variables for resource identifiers — prefer
/api/v2/scripts/customers/get/{customerId}over passing the ID in the request body. This makes URLs more RESTful and easier to log/trace. - Keep
config.urlPathstable — external systems reference the endpoint URL; changing it breaks their integrations. - Protect sensitive endpoints with
execPrivilege— especially for scripts that mutate data or trigger side effects. - Prefer Bearer tokens over Basic auth — Basic auth should only be used as a last resort for legacy integrations.
See also
- Script (reference) — script definition, types, parameter forms, and attributes.
- SpEL Console — developing and debugging SpEL scripts interactively.
- Event Bindings — trigger scripts on entity lifecycle events.
- Script-to-Script Bindings — call one script from another.
- MCP Bindings — expose scripts as AI-callable tools.
- SpEL and Transactions — how transactions affect SpEL scripts.
- Transactions overview — general transaction concepts and strategies.