Skip to content

Commit be5db68

Browse files
authored
fix(agiloft): repoint the block at the alrest surface and fix EWLogin (#6562)
* fix(agiloft): make the block work, and align it with the REST documentation The native Agiloft block could not authenticate against any instance. A customer reported it; production traces for their workspace confirm every failure mode verbatim. Fixing that exposed a second, larger problem, and a per-endpoint audit against the full published documentation found the rest. Authentication - EWLogin sent only $KB/$login/$password as query parameters. A live instance answers `400 EWWrongDataException ... One has to specify $table, $KB, $lang parameters`. $table is required even though only $KB/$login/$password/$lang are documented. Parameters now travel in a form-encoded body, which the docs permit and which keeps the password out of URLs and access logs. - The authentication scheme is read from the login response and trimmed; Agiloft returns it as "Bearer " with a trailing space. - EWLogout was missing $lang. Surfaces - Record create, read, update, search and saved-search now use the endpoints that accept the token EWLogin issues; the legacy operations authenticate from inline credentials, which is what that surface expects. Nothing sends both forms at once — the documented 400 for doing so is what the original report had run into. - EWSelect passes credentials in a POST body, one of the five operations documented to support it. - Attachment retrieval uses the documented EWRetrieve endpoint, with filePosition rather than position, and no longer needs a login/logout pair. Defects found in the audit - remove_attachment reported zero on every call: its body is the EWREST assignment form but the route ran JSON.parse then Number(), yielding NaN. - The EWREST parser could not read EWActionButton's documented response, which puts both assignments on one line. - EWLock treated any 200 as success, including the documented {error, error_description} envelope, and invented an 'UNKNOWN' status. - EWTable discarded the linked-field details, required flag and text field type it had asked for, making includeLinkedInfo inert. - select_records had no result ceiling at all; both it and search now cap and report a truncated flag rather than reporting a capped length as a total. - Optional string inputs rejected null, so a blank Page field failed validation before any request was made. - Upsert treated the documented 202 async acknowledgement as a missing-ID failure, and returned no callback ID for the caller to poll. - Every response contract required an output that the 401 and 500 paths never return. Coverage added - Table and field discovery (EWTable), upsert (EWUpsert), async status (EWAsyncStatus), natural language search (EWNLPSearch), action buttons (EWActionButton), the REPLACE_WITH_ANOTHER delete rule with its substitute records, $async on upsert, and <fieldName>$overwrite on attach. - Reads with a named field list go through the search projection; an unfiltered contract record runs to roughly 184KB and swamps downstream agent context. - Errors are readable: Agiloft wraps failures in HTML around a typed exception and an internal task id, and the JSON endpoints now request real status codes rather than a 200 the caller has to interpret. Not implemented: $searchSQL and $operationHints=NOLOCK are EWRead/EWUpdate parameters and those operations do not run on that surface here; EWQuestion, EWHotlinks, EWOData, EWBroadcast and webhook registration have no documentation beyond their names. Verified against the published documentation, not against a live instance. * fix(agiloft): give natural language search a sentence that paints check:canvas-sentences failed: the nlp_search card resolved to nothing on an untouched canvas, so it painted empty. Its only basic-mode field was the long-input query, and the field list is advanced, so every segment dropped. The sentence now leads with the knowledge base, matching the shape List Tables already uses — both operations are knowledge-base scoped rather than table-scoped, so it also reads more accurately. * fix(agiloft): stop retrying refusals, and expose the outputs the new operations return Five findings from review that had gone unanswered. An Agiloft refusal was surfacing as HTTP 500. readAlrestJson throws when the envelope reports success:false, the route catch mapped that to 500, and the tool runner retries 500s — so a create the server had already rejected could be retried and duplicate the record. Refusals now return a settled failure with the message intact; genuine faults still 500. list_tables could not run in its primary mode. EWTable is knowledge-base scoped, but some instances reject EWLogin without a $table, so whole-knowledge-base discovery failed at login with nothing to fall back to. It now says what the caller can do about it rather than surfacing the raw login error. Upsert corrupted structured values. Every field went through String(), so a multi-value field collapsed into one joined string instead of the documented repeated key/value pairs, and an object silently wrote "[object Object]" into the record. Arrays now encode as repeated pairs and objects are refused, since Agiloft documents no encoding for them. Two outputs were invisible in the editor. `records` was conditioned on search_records alone, so natural language search results could not be chained, and `callbackId` on run_action_button alone, so a queued upsert's callback could not be wired into Async Status even though both values exist at runtime.
1 parent ec8b988 commit be5db68

52 files changed

Lines changed: 3848 additions & 987 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agiloft-spec/SPEC.md

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
# Agiloft REST API — authoritative spec (transcribed from live help.agiloft.com)
2+
3+
## GLOBAL / API Security
4+
- Every REST call should contain credentials as `login={login}&password={password}`.
5+
- POST-body credentials supported ONLY for: /ewws/EWRead, /ewws/EWSelect, /ewws/EWCreate,
6+
/ewws/EWUpdate, /ewws/EWDelete. ("avoid passing the login or password ... by using POST
7+
instead of GET to pass the parameters in the request body")
8+
- JWT: EWLogin returns a token; "The token can then be used in an Authorization request header,
9+
prefixed by the authentication scheme, instead of including the login and password parameters
10+
in following requests." Default scheme Bearer, expiry 15 min (token_expires_in, max 60).
11+
- Statefulness: pattern is "login, do multiple calls, logout". EWLogout terminates the session
12+
associated with the token passed in the Authorization header.
13+
- DELAYS: every REST call has a delay after completion, default 1 second, global var WSDelay.
14+
- Group must be REST-enabled (Setup > System > Manage Web Services > Groups allowed for REST),
15+
else 403.
16+
17+
### General error codes (selected)
18+
- 400 "There is no permissions to access this resource"
19+
- 400 "One has to specify $login and $password parameters or authentication token." <-- BOTH auth methods provided
20+
- 400 "Token is expired"
21+
- 400 "One has to specify $login, $password parameters or use $genhotlink/$genproject pair ..." <-- no auth
22+
- 401 "Wrong Authorization data" <-- invalid authorization scheme
23+
- 401 "Token is blocked" / "User is blocked"
24+
- 403 invalid login attempt / "Authentication failed." / "Invalid login/password combination ..."
25+
- 500 "No active session found for current token"
26+
- 400 "Unable to identify KB with name" / "Cannot find specified knowledgebase: <KBName>"
27+
- 400 "One has to specify $table, $KB, $lang parameters or use $genhotlink/$genproject pair ..."
28+
- 400 "Wrong combination of access token and KB name. No access to data in KB"
29+
- 400 "One has to specify id value."
30+
- 400 "Project <projectId> has not been found" / "Table <tableId> has not been found"
31+
- 400 "No value for 'field' parameter specified."
32+
- 403 "not allowed, please check logs" (IP blacklist)
33+
- 405 "HTTP method <methodName> is not supported by this URL"
34+
35+
## URL CONVENTIONS
36+
- KB names and table names are CASE SENSITIVE (use Logical Table Name).
37+
- REST style: `/ewws/REST/{kbName}/{table}[/{id}]?$login={login}&password={password}&lang={lang}&...`
38+
(omit /{id} for Create)
39+
- GET/POST style: `/ewws/{operation}?$KB={kbName}&$table={table}&$login={login}&password={password}&lang={lang}&...`
40+
"The parameters of the POST request can be inserted into the body of the request to conceal the user credentials."
41+
- Return values: JavaScript eval() form, all names prefixed `EWREST_`. Empty fields returned as nulls.
42+
- JSON decorator: append `/.json` -> `{"success":true,"message":"","result":{...}}`
43+
optional `err_code_resp=1` for real status codes instead of always 200.
44+
- Async decorator: `/ewws/async/EWCreate?...` or `/ewws/EWCreate/.async?...` (EWCreate, EWUpdate, EWDelete)
45+
- Redirect decorator: `/ewws/redirect/...` with $exiturl and $errorurl
46+
- Decorators chain left to right.
47+
48+
## OPERATIONS TABLE (endpoint / methods / returns)
49+
| Create | GET/POST | /ewws/EWCreate | ID of new record |
50+
| Read | GET/POST | /ewws/EWRead | encoded record info |
51+
| Update | GET/POST | /ewws/EWUpdate | encoded record info after update |
52+
| Delete | GET/POST/DELETE | /ewws/EWDelete | nothing |
53+
| Select | GET/POST | /ewws/EWSelect | list of record ids + length |
54+
| Login | GET/POST | /ewws/EWLogin | session token, expiration, auth scheme |
55+
| Logout | GET/POST | /ewws/EWLogout | nothing |
56+
| Search | GET/POST | /ewws/EWSearch | saved search + ad hoc |
57+
| Attach | PUT | /ewws/EWAttach | total files attached |
58+
| RemoveAttached | GET/POST | /ewws/EWRemoveAttachment | nothing |
59+
| RetrieveAttached | GET/POST | /ewws/EWRetrieve | attachment |
60+
| Lock | GET/PUT/DELETE | /ewws/EWLock | lock status |
61+
| AttachInfo | GET/POST | /ewws/EWAttachInfo | attachment info |
62+
| Hotlink | POST | /ewws/EWHotlinks | hotlink |
63+
| Table | GET/POST | /ewws/EWTable | all tables and fields |
64+
| Async Status | GET/POST | /ewws/EWAsyncStatus | execution status |
65+
| GetChoiceLineId | GET | /ewws/GetChoiceLineID | internal id for a choice value |
66+
| Action Button | POST | /ewws/EWActionButton | runs an action button |
67+
| Saved Search | GET/POST | /ewws/EWSavedSearch | saved search details |
68+
69+
NOTE: the operations table lists `/ewws/GetChoiceLineID` and `/ewws/EWActionButton`, but the
70+
detail pages use `/ewws/EWGetChoiceLineId` and `/ewws/async/EWActionButton`. Detail pages carry
71+
working curl examples; the table does not.
72+
73+
## EWLogin
74+
POST /ewws/EWLogin, Content-Type: plain/text.
75+
Params (CAN BE FILLED TO REQUEST BODY): $KB, $login, $password, $lang (default en).
76+
Response JSON: access_token, refresh_token, expiration_time_unit, expires_in, authentication_scheme
77+
(default "Bearer " — NOTE TRAILING SPACE in examples).
78+
Example: POST https://server/ewws/EWLogin?$login=user&$password=passwd&$KB=Demo&$lang=en
79+
Refresh: POST /ewws/EWLogin with Authorization header + body refresh_token=...
80+
Logout: POST or GET /ewws/EWLogout with Authorization header; params $KB, $lang.
81+
Errors: 400 no refresh_token / wrong refresh_token; 401 Refresh Token is expired;
82+
403 "User <userName> lacks permission log in"
83+
84+
## EWCreate
85+
GET/POST /ewws/EWCreate. Content-Type application/x-www-form-urlencoded.
86+
Params in URL/body: $KB, $table, $login, $password, $lang + field values.
87+
Returns: EWREST_id='353';
88+
Async-compatible.
89+
Errors: 400 "Wrong format/value pointed to <columnName>"; linked-field errors.
90+
91+
## EWRead
92+
GET/POST /ewws/EWRead. Params: $KB,$table,$login,$password,$lang,id
93+
Alternative to id: $searchSQL=ext_id='a0B2c345' (must match exactly one record).
94+
Returns EWREST_<field>='<value>'; lines including EWREST_id.
95+
Errors: 400 "no data found for id range(s)"
96+
97+
## EWUpdate
98+
GET/POST /ewws/EWUpdate. Params: ... id=358 + field values.
99+
Alternative to id: $searchSQL.
100+
$operationHints=NOLOCK forces update on a locked record (URL or POST body).
101+
Returns full updated record as EWREST_ lines.
102+
Errors: 400 "One has to specify id or searchSQL value."; constraint violations.
103+
104+
## EWDelete
105+
GET/POST/DELETE /ewws/EWDelete. Params: ... id=358 & deleteRule=...
106+
deleteRule values: ERROR_IF_DEPENDANTS, APPLY_DELETE_WHERE_POSSIBLE,
107+
DELETE_WHERE_POSSIBLE_OTHERWISE_UNLINK, APPLY_UNLINK,
108+
UNLINK_WHERE_POSSIBLE_OTHERWISE_DELETE, REPLACE_WITH_ANOTHER (needs `subs`).
109+
Returns nothing on success; error message on failure.
110+
Errors: 400 "One has to specify deleteRule, id and substitute values.";
111+
409 "Operation cannot be done. Record has <n> dependants" etc.
112+
113+
## EWSelect
114+
GET/POST /ewws/EWSelect. Params: ... where=<sql where clause>
115+
Queries in the URL must use %N equivalent operators.
116+
SQL uses dbname column names. Choice values via GetChoiceLineId.
117+
Limit via DB syntax e.g. "limit 0,200". No sort control (use EWSearch).
118+
Returns: EWREST_id_length = '3'; EWREST_id_0 = '150'; ...
119+
Empty: EWREST_id_length = '0';
120+
Errors: 400 "Error <error> parsing the query <query>"; 500 "Error executing query, please consult logs"
121+
122+
## EWSearch
123+
GET/POST /ewws/EWSearch. Content-Type x-www-form-urlencoded.
124+
Params: $KB,$table,$login,$password,$lang, search=<saved search label>, query=<ad hoc>,
125+
field=<repeated>, page, limit
126+
Operators: = %3D | != %21%3D | ~= %7E%3D (contains) | && %26%26 | || %7C%7C | < <= > >=
127+
Surround each search value in single quotes; if a field label contains spaces, quote the label too.
128+
Empty fields designated with null.
129+
Returns: EWREST_length = '4'; then EWREST_<field>_<i>='value';
130+
Empty: EWREST_id_length = '0';
131+
Pagination: page starts 0. limit 0 = ALL records on page 0.
132+
"The REST interface creates a new session and performs an explicit logout for each call.
133+
As such, though pagination is available, the query will always be rebuilt and rerun."
134+
ALREST EQUIVALENT (documented in this page):
135+
curl --location 'http://localhost:8080/ewws/alrest/CLM Template/case/search?lang=en' \
136+
--data '{ "field": ["id","summary"], "query": "summary=test" }'
137+
Errors: 400 "No search <savedSearch> for table <tableName>"; 400 "No column <columnName> in table <tableName>"
138+
139+
## EWAttach
140+
PUT /ewws/EWAttach. Content-Type multipart/form-data (file in body).
141+
Params: $KB,$table,$login,$password,id,field,fileName
142+
Returns: EWREST_someField.length='1'; (key is `<fieldName>.length`)
143+
Errors: 400 "No value for 'fileName' parameter specified."; forbidden extension etc.
144+
145+
## EWAttachInfo
146+
GET/POST /ewws/EWAttachInfo. Example uses /.json
147+
URL: /ewws/EWAttachInfo/.json?$KB=..&$table=..&$lang=en&field=attached_file&$login=..&$password=..&id=1
148+
Returns: {"success":true,"message":"","result":[{"fileName":"..","size":22126,"filePosition":0}]}
149+
150+
## EWRemoveAttachment
151+
GET/POST /ewws/EWRemoveAttachment. Params: $KB,$table,$login,$password,id,field,filePosition
152+
Returns: the number of attached files remaining in the field.
153+
154+
## EWRetrieve
155+
GET/POST /ewws/EWRetrieve. Params: $KB,$table,$login,$password,id,field,filePosition
156+
Returns: file content in body. Content-Type = the type used when attaching.
157+
158+
## EWLock
159+
GET (status) / PUT (lock) / DELETE (unlock) /ewws/EWLock
160+
Params: $KB,$table,$lang,id (+ $login/$password OR OAuth/JWT). `force` (any value) on DELETE only.
161+
Success JSON: {"id":18,"table_id":2788,"locked_by":"admin","lock_status":"LOCKED","lock_expires_in_minutes":25}
162+
Unlock: {"id":18,"table_id":2788,"lock_status":"NO_LOCK"}
163+
lock_status values: NO_LOCK | LOCKED
164+
Failure JSON: {error, error_description}; codes BAD_REQUEST/UNAUTHORIZED/FORBIDDEN/CONFLICT/SERVER_ERROR
165+
166+
## EWGetChoiceLineId
167+
GET. Detail page URL: /ewws/EWGetChoiceLineId?$KB=..&$login=..&$password=..&$table=case&$lang=en&field=priority&value=High
168+
Returns: EWREST_choiceLineId = '1';
169+
No match -> HTTP 400.
170+
Errors: 400 "No choice line found for value <fieldName>"; 500 unexpected
171+
172+
## EWActionButton
173+
POST ONLY. URL: /ewws/async/EWActionButton?$KB=..&$login=..&$password=..&$lang=en&$table=case&name=ab_field&id=82
174+
Returns: EWREST_id='82'; EWREST_EWCALLBACK_ID='10100_1';
175+
Compatible with EWAsyncStatus.
176+
Errors: 400 "Wrong value for 'sequence' parameter"; 400 "No information for requested column <fieldName>"
177+
178+
## EWAsyncStatus
179+
GET or POST /ewws/EWAsyncStatus. Params: $KB,$login,$password,$lang,$table,callback_id
180+
Returns response CODE only (empty body): 200 completed, 201 queued, 202 in progress,
181+
501 failed, 523 no info for callback id.
182+
183+
## EWSavedSearch
184+
GET/POST /ewws/EWSavedSearch/.json (JSON is the ONLY output; /.json is MANDATORY)
185+
URL must include the logical table name. For POST the table param may be in the body.
186+
MUST be used with EWLogin or OAuth 2.0 authorization. Never asynchronous.
187+
Example: https://localhost:8080/ewws/EWSavedSearch/.json?$table=contract
188+
Returns: {"success":true,"message":"","result":[{"label":"...","name":"...","id":265185,"description":""}]}
189+
190+
## EWTable
191+
GET/POST /ewws/EWTable/.json. Requests x-www-form-urlencoded; returns application/json.
192+
MUST be used with EWLogin or OAuth 2.0 authorization. Never asynchronous.
193+
Params: $KB; optional `table` (plain, NOT $table) = logical name e.g. table=contacts;
194+
includelinkedinfo=true; skipColumnsInfo=true
195+
Example: https://localhost:8080/ewws/EWTable/.json?$KB=Demo&includelinkedinfo=true
196+
Returns: {"success":true,"message":"","result":{"tables":[{"label":"WMI Sample","logicalName":"wmi_sample",
197+
"fields":[{"columnLabel":"ID","columnName":"id","columnType":"BIGINT","columnTypeDomain":"swautoincrementfield"},
198+
{"columnLabel":"Updated By","columnName":"_1794_full_name","columnType":"VARCHAR",
199+
"columnTypeDomain":"swshorttextfield","isLinked":true,
200+
"linkedInfo":[{"linkedTable":"contacts","linkedColumn":"full_name","linkedDao":"_dao3_link0"}],
201+
"textFieldType":"text/plain"}]}]}}
202+
Returns for fields: name, label, type, and required flag. Linked fields return only source table info.
203+
Action buttons, related tables, embedded search results, embedded communications NOT supported.
204+
205+
## EWUpsert
206+
POST /ewws/EWUpsert. Content-Type application/x-www-form-urlencoded.
207+
Authentication: Required ($login and $password).
208+
Async support: Yes (EWAsyncStatus) via $async.
209+
System params: $KB*, $table*, $login*, $password*, $match*, $lang, $async
210+
Remaining params are record fields.
211+
Matching: no match -> create; one match -> update; multiple -> error.
212+
Example body: $KB=Demo / $table=contacts.employees / $login=admin / $password=qwerty /
213+
$lang=en / $match=_login / _login=jdoe / first_name=John / ...
214+
Returns: EWREST_id='353';
215+
Status: 200 updated, 201 created, 202 accepted(async), 400, 401, 403, 404,
216+
409 Conflict (multiple matching records), 500
217+
218+
## EWNLPSearch
219+
Content-Types: application/json, application/x-www-form-urlencoded
220+
Params: $KB*, $login*, $password*, $lang, field[]* , nlp_query*, page, limit
221+
Returns same format as REST-Search.
222+
Example return: {"success":true,"message":"","result":[{"company_name":"...","id":31,...}]}
223+
224+
## DATA ENCODING
225+
- Choice fields: text value as in GUI (`&country=USA`). For EWSelect ad hoc queries use
226+
GetChoiceLineId IDs instead.
227+
- Multi-choice: repeated key/value pairs.
228+
- Elapsed time: "days:hours:minutes:seconds" e.g. "0:1:35:15"
229+
- Linked fields: Query By Example with ':' qualifier (`&company_name=:Agiloft` or
230+
`&company_name=Company:Agiloft`). ':' and '?' in values escaped with backslash.
231+
SQL sub-select form uses '?' qualifier.
232+
- File/image fields: POST with enctype multipart/form-data; form field name = file field name;
233+
`fieldName$overwrite` to replace rather than add.

0 commit comments

Comments
 (0)