Skip to content

Commit 852b01c

Browse files
committed
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.
1 parent 1dd85eb commit 852b01c

52 files changed

Lines changed: 3587 additions & 986 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)