tSM Public API (v2) Guide
tSM is a modular, microservice-based platform with publicly documented APIs for integrating with external systems (CRM, ERP, portals). It is intended for entity operations, workflow triggering, and data access via secure endpoints.
Who is this API for?
- Non-technical users / Business analysts: simple list reads, filtering, and form lookups.
- Integrators / Developers: full CRUD, bulk operations, pagination, relationship expansion, correlation headers, and request tracing.
How to call the API
Base URLs and HTTP methods
Each module has its own base URL (e.g., 'https://yourServerUrl/tsm-config-form/api/v2/', 'https://yourServerUrl/tsm-customer/api/v2/', …). All entities follow consistent REST patterns:
| Operation | Method | URL pattern |
|---|---|---|
| Create | POST | /api/v2/{entity} |
| Read (detail) | GET | /api/v2/{entity}/{id} or {idOrCode} or {idOrKey} |
| Full update | PUT | /api/v2/{entity}/{id} or {idOrCode} or {idOrKey} |
| Partial update | PATCH (JSON Merge Patch) | /api/v2/{entity}/{id} or {idOrCode} or {idOrKey} |
| Delete | DELETE | /api/v2/{entity}/{id} or {idOrCode} or {idOrKey} |
| Bulk operations | GET/POST/PUT/PATCH/DELETE | /api/v2/{entity}/bulk |
| Paginated listing | GET | /api/v2/{entity}/page |
| Quick search | GET | /api/v2/{entity}/search |
PATCH = JSON Merge Patch (RFC 7396) — send only the changes; setting a field to
nullremoves it. [RFC Editor]: https://www.rfc-editor.org/rfc/rfc7396
Authentication & headers
Authorization: Bearer <JWT>(typical)X-Request-Id: unique request identifier (useful for debugging)X-Correlation-Id: correlate calls across services- Trace Context (optional):
traceparent,tracestateper W3C for end-to-end distributed tracing. [W3C]: https://www.w3.org/TR/trace-context/
Date/time format
Use ISO 8601 and UTC “Z” (e.g., 2025-09-26T12:34:56Z). [Wikipedia]: https://en.wikipedia.org/wiki/ISO_8601
Listing, Search, and Pagination
tSM provides two universal endpoints: /search and /page. Both support sorting, expansion, and multiple filters.
/search vs /page — when to use which
| Capability | /search | /page |
|---|---|---|
| Primary purpose | quick lookups, dropdowns, exports | tabular views, lazy loading |
| HTTP method | GET | GET |
| Filtering | yes | yes |
| Pagination params | size (max number of items) | page + size |
Total count (totalElements) | usually no | yes |
Sorting (sort) | yes | yes |
Expansion (expand) | yes | yes |
Page parameters follow the common Spring Data convention: page, size, and repeated sort=field,ASC|DESC. [Stack Overflow]: https://stackoverflow.com/
Common parameters
- Sorting:
sort=name,ASC&sort=createdAt,DESC(use multiplesortparams as needed). - Relationship expansion:
expand=ROLES&expand=GROUPS(exact values depend on the entity/module and are listed in that module’s spec). - Filtering:
field__op=value(operator form)
Filter operators (consistent across the API)
In tSM Public API v2, filtering is expressed using the syntax:
field__operator = value
Multiple filters in a request combine via AND logic by default (i.e. all must match). Below is a detailed explanation of each operator type, what it does, and examples.
Comparisons
Operators for numeric, date, or comparable fields.
| Operator | Meaning | Example | Explanation |
|---|---|---|---|
__eq | equal to | status__eq=ACTIVE | select records where status exactly equals “ACTIVE” |
__noteq | not equal | status__noteq=DELETED | exclude records with status = “DELETED” |
__gt | greater than | amount__gt=1000 | amount strictly greater than 1000 |
__gte | greater than or equal | amount__gte=1000 | amount ≥ 1000 |
__lt | less than | amount__lt=500 | amount strictly less than 500 |
__lte | less than or equal | amount__lte=500 | amount ≤ 500 |
Text / String operators
These operators work on string (text) fields, for substring matching, prefix/suffix, etc.
| Operator | Meaning / Behavior | Example | Explanation |
|---|---|---|---|
__like | contains substring (wildcard match) | name__like=%25Acme%25 | select names containing “Acme” anywhere (%25 is URL-encoded %) |
__contains | alias for “contains” (similar to __like) | description__contains=test | same as description__like=%25test%25 |
__notcontains | does not contain substring | note__notcontains=error | exclude texts containing “error” |
__startswith | begins with given substring | code__startswith=REG. | codes that start with “REG.” |
__endswith | ends with given substring | email__endswith=@domain.com | emails ending with “@domain.com” |
Important: when using % in __like or similar, you must URL-encode % as %25, e.g.:
value__like=%25Mgr%25
This matches “Mgr” anywhere in the field.
Lists & (non)emptiness
Used when dealing with multi-valued fields (arrays) or checking empty/non-empty status.
| Operator | Meaning | Example | Explanation |
|---|---|---|---|
__in | field value is in the given list | status__in=ACTIVE,PENDING | include records whose status is either “ACTIVE” or “PENDING” |
__notin | field value not in the given list | status__notin=DELETED,ARCHIVED | exclude records whose status is either “DELETED” or “ARCHIVED” |
__empty | field is empty or null | description__empty=true | select records where description is null or empty |
__notempty | field is non-empty (has some value) | remarks__notempty=true | select records where remarks is not null/empty |
Ranges & Time (including relative)
These operators handle ranges (between) and time-based filtering. Very useful for dates or numeric ranges.
| Operator | Meaning / Behavior | Example | Explanation |
|---|---|---|---|
__btw | between two values (inclusive or as backend) | amount__btw=100,500 | select amounts between 100 and 500 |
__btw with dates | between two timestamps | createdAt__btw=2025-09-25T00:56:37.851Z,2025-09-26T00:56:37.851Z | select records created in year 2025 |
__btwr | reverse between (backend-specific support) | (only use if API supports) | same as btw but reversed semantics in some modules |
Relative time (via __gtr, __ltr) | filter relative to “now” | updatedAt__gtr=-P7D | records updated in last 7 days |
Duration syntax (ISO 8601): durations are expressed as P[n]Y[n]M[n]DT[n]H[n]M[n]S, e.g. PT2H30M for 2 hours 30 minutes. [Wikipedia]: https://en.wikipedia.org/wiki/ISO_8601
A prefix “–” (minus) can signal backward duration.
Example:
updatedAt__gtr=-P7D
This means “updatedAt greater than (now minus 7 days)”, i.e. last 7 days.
Example summary in one request:
GET /api/v2/customers/page?
status__eq=ACTIVE&
name__contains=Tech&
whenInserted__btw=2025-09-25T00:56:37.851Z,2025-09-26T00:56:37.851Z&
revenue__gt=10000&
remarks__notempty=true&
sort=whenInserted,desc&
page=0&
size=50
This would return customers who:
- have
status = ACTIVE - whose
namecontains “Tech” - were created between 2025-09-25T00:56:37.851Z and 2025-09-26T00:56:37.851Z
- have
revenue > 10000 - and non-empty
remarks - sorted descending by creation date
- first page, 50 items per page
Practical examples
1) Quick lookup (/search)
GET /api/v2/register-values/search?
register.code__eq=Reg.Crm.Titul.Pred.Menom&
size=100
2) Paginated view with filter & sorting (/page)
GET /api/v2/register-values/page?
register.code__eq=Reg.Crm.Titul.Pred.Menom&
value__like=%25Mgr%25&
page=0&
size=20&
sort=order,asc
Pagination & sorting follow the Spring Data convention. [Docs Spring Data]: https://docs.spring.io/spring-data/rest/reference/paging-and-sorting.html
3) Filter by related entity ID
GET /api/v2/register-values/search?
register.id__eq=019970a1-7797-73ba-a6d5-b424443a7dcb
4) Non-empty values
GET /api/v2/register-values/page?
value__notempty=true&page=0&size=50
5) Multiple filters + sorting
GET /api/v2/register-values/page?
register.code__eq=Reg.Crm.Type.Partner&
value__contains=VIP&
sort=order,desc&
size=30
CRUD & Bulk — quick reference with templates
Create (POST)
curl -X POST "https://<module-host>/api/v2/customers" \
-H "Authorization: Bearer <JWT>" \
-H "Content-Type: application/json" \
-d '{"code":"CUS-001","name":"Acme s.r.o.","active":true}'
Detail (GET + expand)
curl "https://<module-host>/api/v2/users/U123?expand=ROLES&expand=GROUPS" \
-H "Authorization: Bearer <JWT>"
Full update (PUT)
curl -X PUT "https://<module-host>/api/v2/entity-catalogs/CAT-001" \
-H "Authorization: Bearer <JWT)" \
-H "Content-Type: application/json" \
-d '{"id":"…","code":"CAT-001","name":"Internet 300","active":true}'
Partial update (PATCH, JSON Merge Patch)
curl -X PATCH "https://<module-host>/api/v2/tickets/T-1001" \
-H "Authorization: Bearer <JWT>" \
-H "Content-Type: application/merge-patch+json" \
-d '{"assignee":null,"tags":["VIP","urgent"]}'
Behavior per RFC 7396. [RFC Editor]: https://www.rfc-editor.org/rfc/rfc7396
Bulk (GET/POST/PUT/PATCH/DELETE)
GET /api/v2/{entity}/bulk?idOrCodes=…&idOrCodes=…POST /bulk— array of new objectsPUT /bulk— array of full objectsPATCH /bulk— array of{ id|code, patch:{…} }DELETE /bulk?idOrCodes=…
Best practices
- Prefer PATCH for routine updates — send only the diff; fewer conflicts. (RFC 7396) [Datatracker IETF]: https://datatracker.ietf.org
- Use UTC + ISO 8601 consistently; let the UI handle local time conversion. [Wikipedia]: https://en.wikipedia.org/wiki/ISO_8601
- Keep page/size/sort per the common Spring Data convention — most clients and tools expect it. [Stack Overflow]: https://stackoverflow.com/
- Expand relationships deliberately — reduce round trips, but avoid over-fetching.
- Trace requests — send
X-Request-Id/X-Correlation-Idand, where possible, W3C Trace Context (traceparent,tracestate) for end-to-end tracing. [W3C]: https://www.w3.org/TR/trace-context/ - For LIKE/contains, remember to URL-encode
%→%25.
Advanced filter syntax & combining conditions
Basic filter parameter shape
-
Syntax:
field__operator=valuefield: entity attribute name (e.g.,name,status,createdAt)operator: any supported operator (eq,noteq,contains,in, etc.)value: a single value, orvalue1,value2(forin,btw)- If you filter on an unknown field, expect 400 Bad Request with an explanatory message.
Combining multiple filters (AND)
Multiple filters are combined with AND by default (all must match).
Example: status_eq=ACTIVE&name__contains=acme → only records where status = ACTIVE and name contains acme.
Ranges / “between”
-
__btw— two comma-separated values (e.g.,10,100or two timestamps)- Example:
amount__btw=100,500 - Dates:
createdAt__btw=2025-09-25T00:56:37.851Z,2025-09-26T00:56:37.851Z
- Example:
-
__btwr— “between reversed”; use only if your backend supports it.
Relative time filters
__gtr— greater-than relative (e.g.,updatedAt__gtr=-P7Dfor “last 7 days”)__ltr— less-than relative- Use ISO 8601 duration for relative values (e.g.,
-P7Dmeans 7 days back). [Wikipedia]: https://en.wikipedia.org/wiki/ISO_8601
Text operators
-
__like— contains substring (URL-encode%as%25)- Example:
value__like=%25Mgr%25
- Example:
-
__contains— alias for a contains-style match -
__notcontains,__startswith,__endswith
Relationship expansion (expand)
Expanding returns related objects/fields inline rather than just identifiers.
- Syntax:
?expand=REL1&expand=REL2 - Each module defines its own allowed
expandvalues in OpenAPI (e.g.,ROLES,GROUPS,REGIONSforUser). - Expansion reduces follow-up calls and simplifies clients.
Example:
GET /api/v2/users/U123?expand=ROLES&expand=GROUPS
The response will include expanded roles and groups rather than only their IDs.
Error handling — status codes & error payloads
| HTTP code | Meaning | Typical cause |
|---|---|---|
| 400 Bad Request | invalid parameter/filter/schema | e.g., filter on an unknown field |
| 401 Unauthorized | missing or invalid token | improperly authorized request |
| 403 Forbidden | insufficient permissions | attempt to modify unauthorized data |
| 404 Not Found | entity not found | GET /api/v2/users/xxx where xxx doesn’t exist |
| 409 Conflict | version conflict, duplicate code | PUT/POST with a uniqueness conflict |
| 422 Unprocessable Entity | validation errors in request body | missing required fields, wrong types |
| 500 Internal Server Error | unexpected server error | internal exceptions |
Error response (example)
{
"timestamp": "2025-09-25T00:56:37.851Z",
"status": 400,
"error": "Bad Request",
"message": "Unknown filter field 'foo'",
"path": "/api/v2/users"
}
Some modules may return detailed errors:
{
"status": 422,
"error": "Unprocessable Entity",
"errors": [
{ "field": "name", "message": "must not be blank" },
{ "field": "code", "message": "duplicated" }
]
}
Always document, per module, which fields can fail validation and the expected messages.
Security & limits
Token expiration
- JWTs have an expiry (e.g., 15 minutes, 1 hour).
- The client must handle refresh tokens or re-authentication on expiry.
Rate limiting
- Modules may enforce request limits (e.g., 100 req/s).
- On limit breach, servers respond with 429 Too Many Requests; apply backoff + retry.
Authorization
- Tokens should carry claims describing user identity and permissions (modules & operations: read, write, delete).
- Some fields/operations are restricted to admins or privileged roles.
Extended endpoints & special cases
/search as a GET lookup
- Use when you don’t need full pagination — quick lists for lookups/autocomplete.
sizesets the maximum number of results returned.
/page for full listings
- Use for data tables, lazy-loaded UI, full paging, and total count (
totalElements). - Responses contain
page,size,totalPages,totalElements, andcontent.
bulk endpoints
- For mass operations (import/sync).
- Mind request size limits (item count, max payload).
- On per-item errors, some modules may fail the whole bulk or return partial failures — document each module’s behavior.
Additional endpoints
/log— audit trail for a given entity/save-diff— upsert by differences (send only the change set)/searchvs customfilter— somesearchendpoints may implement extra features (e.g., full-text index, relevance)