# Endpoints Endpoints are REST WebServices written in ES5 JavaScript, executed on the Rhino engine. Each file must export a single `WebService` instance. Vitruvio maps incoming HTTP requests to the corresponding handler method. --- ## vitruvio.json Registration ```json { "key": "my-endpoint", "name": "My Endpoint", "description": "What this endpoint does.", "language": "javascript", "authMode": "PUBLIC", "active": true, "source": "endpoints/my-endpoint.js" } ``` - `key` — kebab-case, stable. Changing it breaks any external integrations pointing to the URL. - `authMode` — one of `"PUBLIC"`, `"STATIC_TOKEN"`, `"VITRUVIO_WS_USER_AUTH"`, `"HTTP_BASIC_AUTH"`. Default to `"PUBLIC"`. - `active` — must be `true` for the endpoint to be reachable. Set to `false` to disable without removing. ### URL structure The `authMode` determines the URL segment used to reach the endpoint: | authMode | URL | |---|---| | `PUBLIC` | `/api/integration/public/{key}` | | `STATIC_TOKEN` | `/api/integration/tokenauth/{key}` | | `VITRUVIO_WS_USER_AUTH` | `/api/integration/bearerauth/{key}` | | `HTTP_BASIC_AUTH` | `/api/integration/bauth/{key}` | ### STATIC_TOKEN authentication The token can be sent in either of two ways: - Query parameter: `_x_token_auth=` - Request header: `X-WS-TOKEN-AUTH: ` --- ## File Structure ```javascript function WebService() { this.onGet = function(params) { ... } this.onPost = function(params) { ... } this.onPut = function(params) { ... } this.onPatch = function(params) { ... } this.onDelete = function(params) { ... } } module.exports = new WebService(); ``` - All methods are **optional**. Only implement the verbs your endpoint actually handles. - Remove unused verb stubs entirely — don't leave placeholder `return JSON.stringify({ prop: value })` bodies in production code. - `module.exports` must be `new WebService()` — Vitruvio instantiates and reads from this exported object. - If a caller sends a verb that is not implemented in the script, Vitruvio throws `EndpointMethodNotImplementedException` — it is not a silent 404. --- ## The `params` Object Each handler receives a single `params` argument: ```javascript // GET / DELETE params = { headers: {}, // request headers as key/value query: {} // query string params as key/value e.g. params.query["hub.mode"] } // POST / PUT / PATCH params = { requestBody: '', // raw request body string — always JSON.parse() before use headers: {}, query: {} } ``` **Always parse the body explicitly:** ```javascript this.onPost = function(params) { var body = JSON.parse(params.requestBody); // use body.* } ``` --- ## Return Values | Return value | HTTP response | |---|---| | A non-empty string | `200 OK` with that string as body | | `null`, no return, or empty string `""` | `204 No Content` | | `JSON.stringify(obj)` | `200 OK` with JSON body — set Content-Type accordingly | Always return strings. Returning a raw object will not serialize correctly. ```javascript // Correct return JSON.stringify({ status: 'ok', data: result }); // Wrong — object will not be serialized properly return { status: 'ok' }; ``` --- ## Error Handling Wrap handler logic in `try/catch` and return a JSON error body. Don't let uncaught exceptions bubble up. ```javascript this.onPost = function(params) { try { var body = JSON.parse(params.requestBody); if (!body.id) throw "Missing required field: id"; // ... return JSON.stringify({ success: true }); } catch (e) { return JSON.stringify({ error: e.toString() }); } }; ``` - Use `e.toString()` when serializing caught exceptions — raw exception objects may not stringify correctly under Rhino. - Validate required fields early and throw descriptive messages. --- ## DELETE Convention By convention, DELETE requests should not receive a body and should not return data. If nothing is returned, Vitruvio responds with `204 No Content`. ```javascript this.onDelete = function(params) { // perform deletion // no return needed — 204 is implied } ``` --- ## Loading Scripts and Libraries Use `libService.loadScript(key)` to load scripts registered in `vitruvio.json`. The `key` is the script's `key` field in the manifest. ```javascript // Load inside the handler — safe, always fresh this.onPost = function(params) { var lib = libService.loadScript('my-lib'); lib.doSomething(); } // Load in the constructor — shared across all calls to this endpoint instance function WebService() { var lib = libService.loadScript('my-lib'); this.onPost = function(params) { lib.doSomething(); } } ``` **Prefer constructor-level loading** when the same library is used in multiple handlers — avoids redundant loads. Use handler-level loading when the script key is dynamic or determined at runtime. --- ## Database Access Load the `db` library and instantiate with a datasource name: ```javascript var db = libService.loadScript('db'); var banco = new db(db.VITRUVIO_DATASOURCE); // default Vitruvio datasource // or var banco = new db('named-datasource'); ``` Common operations: ```javascript // Single row var row = banco.queryRow("SELECT NAME FROM TB_CONFIG WHERE ID = :id", { id: 1 }); // Multiple rows var rows = banco.query("SELECT * FROM TB_ITEMS WHERE ACTIVE = 1"); // Update / Insert banco.update("UPDATE TB_ITEMS SET STATUS = :status WHERE ID = :id", { status: 'DONE', id: itemId }); ``` - Prefer named queries from `queries/*.sql` for reusable or complex SQL. Use inline SQL only for simple, endpoint-specific queries. - Use named bind parameters (`:paramName`) — never concatenate user input into SQL strings. --- ## Logged User (Authenticated Modes) For `VITRUVIO_WS_USER_AUTH` and `HTTP_BASIC_AUTH`, Vitruvio resolves the caller's identity and injects a `loggedUser` object into the script context. This gives the endpoint access to who is making the call. `HTTP_BASIC_AUTH` additionally validates that the authenticated user belongs to the roles configured for that endpoint in Vitruvio's admin. --- ## Common Patterns ### Webhook with verification (e.g. Meta/WhatsApp) ```javascript this.onGet = function(params) { var mode = params.query["hub.mode"]; var token = params.query["hub.verify_token"]; var challenge = params.query["hub.challenge"]; if (mode === "subscribe" && token === EXPECTED_TOKEN) { return challenge; } return JSON.stringify({ error: "Forbidden" }); }; ``` ### Delegating to a script ```javascript this.onPost = function(params) { try { var body = JSON.parse(params.requestBody); var lib = libService.loadScript('my-processing-script'); var result = lib.run(body); return JSON.stringify(result); } catch (e) { return JSON.stringify({ error: e.toString() }); } }; ``` --- ## What NOT to do - Don't leave placeholder bodies (`return JSON.stringify({ prop: value })`) in unused verb stubs — remove the whole method instead. - Don't return raw exception objects — always call `.toString()` or wrap in a message. - Don't hardcode datasource names or tokens in the script — read them from `vConfigService` or from a DB config table. - Don't put heavy business logic directly in the endpoint file — delegate to a script via `libService.loadScript`.