Skip to main content
Version: 2.4

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

  1. Routing — the platform matches the request path to a binding row by config.urlPath.
  2. Authentication — the request must carry a valid Authorization header (see Authentication & authorization below).
  3. Validation — if the script defines a paramsFormCode, the request body is validated against its JSON Schema before execution.
  4. Execution — the script runs with the request body fields and any path variables injected as context variables.
  5. 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}"
}
}
FieldTypeRequiredDescription
config.scriptstringCode of the target script in the Scripts register.
config.urlPathstringURL 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.urlPathEndpoint 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:

SourceVariableExample 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}"
}
}

ScriptCustomer.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

DirectionFormat
RequestJSON body → injected as named variables into the script context (each top-level key becomes #key). Path variables are injected the same way.
ResponseThe 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 fieldEffect on REST binding
paramsFormCodeThe JSON Schema validates the request body before the script executes. Invalid payloads are rejected with a 400 Bad Request and a descriptive error.
resultFormCodeDocuments 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:

  1. Validation — the request body is checked against the schema; type, format, and required-field constraints are enforced automatically.
  2. Documentation — field descriptions, labels, and constraints serve as a machine-readable API contract for external consumers.
tip

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

ScriptDemo.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

  1. Set method to POST and URL to https://tsm.example.com/api/v2/scripts/<urlPath>.
  2. In the Authorization tab, choose Bearer Token and paste your token.
  3. In Headers, add X-Tsm-Tenant: default (if multi-tenant).
  4. 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.

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:

MethodUse-case
OAuth 2 client credentialsFor 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 codeFor user-facing applications where the end-user authenticates interactively.
Personal access tokenIf 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)>
warning

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:

  • execPrivilege on 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-Tenant header must be present.

Error handling

HTTP statusCondition
400Request body fails paramsFormCode validation.
401Missing or invalid authorization credentials.
403Token lacks the script's execPrivilege.
404No binding row matches the path.
500Script 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.urlPath stable — 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