API & Integration Documentation

1. API Overview

The Northbound API exposes a RESTful API that allows clients to:

  • Authenticate and obtain JWT tokens

  • Manage device provisioning and configuration

  • Query device status and information

  • Configure network services

  • Monitor service status

2. API Design Principles

The API follows these design principles:

  • RESTful: Resources are identified by URLs and manipulated using standard HTTP methods

  • Stateless: Each request contains all necessary information

  • JSON-based: Request and response payloads use JSON format

  • Versioned: API versioning supports backward compatibility

  • Documented: OpenAPI/Swagger documentation is provided

JSON request field names are matched case-insensitively, matching the legacy .NET REST API. The canonical casing shown in the OpenAPI/Swagger schema is PascalCase (for example Parameters, Key, Value, Creator), but lowercase or mixed-case variants deserialize identically.

Breaking change in 1.0.1 — PUT /api/UserInfo custom parameters are now read from Parameters, not Params (DEV-2620).

The custom key/value list on PUT /api/UserInfo was bound from a request field named Params. That name was a porting typo: the .NET REST API names the field Parameters (SetUserInfoRequest.Parameters), as does every other endpoint in this API and the canonical casing documented above. The Java port now accepts Parameters, and no longer accepts Params.

Because unknown JSON fields are ignored rather than rejected, a client that keeps sending Params is not given an error — its parameters are silently discarded. Review any integration that calls this endpoint.

Custom parameter keys are validated: only cust11cust20, latitude, and longitude are accepted, and latitude/longitude must parse as a number. Because the list now binds, that validation now applies to it — see the second row below.

Request body Before After .NET

Parameters (accepted keys only) + empty UserInfo

ErrorCode 203

ErrorCode 100, values written

100

Parameters containing a key outside cust11cust20 / latitude / longitude, or a non-numeric latitude/longitude — with a non-empty UserInfo (e.g. LoginName)

100, UserInfo written, the list ignored as an unknown field

ErrorCode 203, nothing written

203

Params + empty UserInfo

100, values written

ErrorCode 203

203

Params + non-empty UserInfo (e.g. LoginName)

100, parameters written

100, parameters silently dropped

100 (dropped)

parameters / PARAMETERS (any casing)

203

100

100

params / PARAMS (any casing)

100

203

203

The second row is the most destructive transition: a request that previously succeeded and wrote its UserInfo fields now fails outright and writes nothing, because the parameter list it carries reaches key validation for the first time. Verify that every key sent on this endpoint is within the accepted set before upgrading.

The rename restores parity with the .NET contract rather than diverging from it, so it is not listed under Differences from Legacy .NET API: after the change the After column matches the .NET column on every row above. The 203 responses are not produced by the rename; they come from validation that already existed in the Java port, that the rename merely makes reachable for Parameters bodies, and that the .NET reference performs identically:

  • rows 3 and 6 — and row 1’s previous 203 — the all-inputs-null guard, which rejects a request carrying no user-info field and no custom parameter. The .NET reference applies the same guard inside the BLL method both of its transports delegate to, over the same eight fields and the same empty custom-parameter map, with the same message text;

  • row 2 — the custom-parameter-name whitelist, which the .NET reference validates identically.

It completes DEV-2419, which corrected the same typo on the response side.

3. Authentication

The Northbound API is secured via a custom JWT filter (FTApiJwtFilter) that intercepts incoming HTTP requests and delegates token validation to the AuthService.

3.1. Token Extraction

  • The filter reads the authentication token from the HTTP header named by Authorization(IOT_AUTH_HEADER defined in CommonRegistry).

  • Requests to /api/System/Token, non-API endpoints, and HTTP POST requests are excluded from filtering.

3.2. Token Validation

  • For protected endpoints, the filter invokes: authService.authorizeFromHeader(token) to validate the token and populate the Spring Security context.

3.3. Unauthorized Handling

  • After the filter chain, if the response status is 401 Unauthorized and no WWW-Authenticate header is present, the filter adds: WWW-Authenticate: Basic realm="<soapUrl>" where soapUrl is injected from the api.soap.url property.

  • If AuthService throws FriendlyUnauthorizedUserException, the filter:

    • Clears the SecurityContextHolder.

    • Sets response status to 401.

    • Sets Content-Type to application/json.

    • Serializes an error response via Jackson ObjectMapper using: authService.buildErrorResponse(e)

3.4. Configuration

  • api.soap.url (in api.properties) sets the SOAP base URL and realm for authentication challenges.

  • Ensure FTApiJwtFilter is registered as a Spring component to apply this security logic to all API requests.

4. API Endpoints

For detailed API documentation, please refer to the OpenAPI/Swagger documentation available at:

The Swagger UI provides:

  • Interactive documentation for all API endpoints (REST and SOAP)

  • Request and response schemas for each endpoint

  • The ability to try out API calls directly from the browser

  • Authentication requirements and methods

  • Parameter specifications and constraints

The Swagger UI is the most up-to-date source of API documentation as it is automatically generated from the application’s code annotations and includes the full SOAP API specification.

5. Error Response Format

All error responses follow a standardized format.

5.1. SOAP/XML Format

<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xmlns:xsd="http://www.w3.org/2001/XMLSchema"
               xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
   <soap:Body>
      <ServerWSResponse xmlns="http://www.friendly-tech.com">
         <ServerWSResult>
            <ErrorCode>100</ErrorCode> <!-- 100 = success; other codes = failure -->
            <Message/>              <!-- error message, if any -->
         </ServerWSResult>
      </ServerWSResponse>
   </soap:Body>
</soap:Envelope>

5.2. JSON Format

{
  "errorCode": 100,
  "message": ""
}

The same error codes and meanings apply in JSON responses.

Error Code

Enum

Description

100

SUCCESS

200

FAIL

Operation failed

201

NO_CPE

No CPE

201

USER_EXISTS

Username already exists

201

USER_NOT_EXISTS

Username does not exist

202

NO_DB

No database available

203

INCORRECT_REQUEST

Incorrect request

203

INCORRECT_REQUEST_STRUCTURE

Incorrect request structure or non-writable parameter

204

GENERAL

General error

205

NO_ACS

No ACS available

206

NO_CPE_NEW_SN

No CPE for new serial number

207

NO_CPE_CURR_SN

No CPE for current serial number

208

NO_TRANSACTIONS

No transactions found (Java-only; see Unified "No transactions found" Error (ErrorCode 208))

401

UNAUTHORISED

Wrong username or password

ErrorCode=208 (NO_TRANSACTIONS, No transactions found) is a Java-only code introduced by DEV-2484 (with PO sign-off). It does not exist in the legacy .NET API, which used 203 for the same transaction-not-found cases. See Unified "No transactions found" Error (ErrorCode 208).

6. Validation

The API implements validation for request payloads:

  • Required Fields: Fields marked as required must be present

  • Field Formats: Fields must have the correct format (e.g., IP addresses, MAC addresses)

  • Enumerations: Fields with enumerated values must contain valid values

  • Range Constraints: Numeric fields must be within valid ranges

Business-rule validation failures are reported in the response body as an ErrorCode in the 20X range with an HTTP 200 status, not as a 400 Bad Request — see Error Response Format. A malformed JSON body (one that fails to parse at all) is treated the same way: it returns ErrorCode=203 (Incorrect request structure) with an HTTP 200 status — see Malformed JSON Body Rejected Uniformly (ErrorCode 203).

A value that cannot be read as a whole number is coerced to null by the lenient deserializer wired on the fields listed below. On these fields a non-numeric string ("AmountOfDays": "one"), an empty or blank string, a fractional value, an object, an array, and a number outside the field’s range are all treated as if the field had been absent, rather than aborting the body read. This matches the legacy .NET binder, which leaves a member that fails to bind at its default and continues processing the request.

Field Endpoint(s) Result of an unbindable value

TransactionId

POST /api/Transaction, POST /api/Diagnostic, POST /api/Parameter/Transaction

ErrorCode=203, TransactionId must be a positive number — see TransactionId Validation

AmountOfDays

POST /api/Device/Activity, POST /api/Device/History

ErrorCode=100 — the value means "no offset" and the default time window of the current day applies

On AmountOfDays a fractional literal is therefore coerced too: "AmountOfDays": 7.5 yields the current-day window, not a 7-day one. Earlier builds silently truncated it to 7; the legacy .NET binder rejects a fractional value for an int? member, so the truncation was the divergence.

The outcome after coercion differs per field because the binding layer decides nothing: it only produces null, and the field’s own downstream validation decides the rest. TransactionId has an explicit positive-number guard; AmountOfDays has no rejecting guard and simply falls back to its default window.

Fields outside this table are not covered by the mechanism, and their behavior has to be read per field and per endpoint rather than assumed from the table. Two worked examples, both for TransactionId bound without the lenient deserializer:

  • DELETE /api/Task/Pending — an empty TransactionId binds to null and is treated as "no transaction filter", deleting all pending tasks for the device.

  • POST /api/Task/Pending/Transaction — an empty TransactionId also binds to null, but this endpoint rejects it with ErrorCode=203 and TransactionId must be a positive number.

Extending the coercion to the remaining numeric fields is tracked as a follow-up.

7. Differences from Legacy .NET API

The Java Northbound API is a port of the legacy .NET Northbound API. Wherever practical, behavior is kept identical. In a small number of places the Java port intentionally diverges from the .NET reference to eliminate historical inconsistencies, clarify contracts, or simplify maintenance. This section documents those intentional differences so integrators relying on the legacy behavior can update their clients.

Governing principle. Port parity is the default, not an absolute rule:

  • Where the original behavior is reasonable, the Java port copies it exactly.

  • Where the original behavior contradicts logic, consistency, or sanity, the Java port keeps the saner behavior and documents the difference here.

The goal is a correct, maintainable API — not a bug-for-bug replica. Every entry below is an intentional, reviewed deviation, not an oversight.

As a rule, only Message text and HTTP status code semantics differ; ErrorCode values remain identical to the .NET contract, so clients that key off ErrorCode require no changes. A few documented cases are the exceptions: operations that reject a parameter with an absent or empty Name (FTAddObject, FTSetParameters, FTCreateDiagnostic), where the Java port returns the structurally correct 203 instead of the .NET catch-all 204; FTGetCPEByIP with a malformed stored STUN address, where the Java port returns 203 instead of 204; a whitespace-only serial number, where the Java port returns 203 instead of 201; and — the one case that introduces an entirely new code — a transaction that is not found on Task/Pending/Transaction, Transaction, and Diagnostic (get-by-transaction), where the Java port returns the new Java-only 208 (NO_TRANSACTIONS, No transactions found) instead of the .NET 203 (DEV-2484, PO sign-off; 208 does not exist in the .NET contract, whose ResultXml codes run 200207). Two further exceptions are cases where the Java port succeeds on input the .NET REST transport rejects, in both cases by adopting the behavior the .NET SOAP transport already had: a device-mutation request that omits the UserInfo block but carries custom parameters returns 100 instead of the .NET REST 204, and a latitude or longitude written with a comma decimal separator returns 100 — on the write path instead of the .NET REST 203, and on the search path instead of the 204 both .NET transports return on FindDeviceAdvanced / FTFindDeviceAdvanced and the 203 .NET REST returns on Device/UserInfo (.NET SOAP FTFindDevice already answers 100 there). One further exception is another case of the Java port returning the structurally correct 203 in place of a .NET catch-all: an unparsable latitude or longitude search filter returns 203 on FindDeviceAdvanced / FTFindDeviceAdvanced, where both .NET transports return the catch-all 204; on Device/UserInfo / FTFindDevice .NET already returns 203, so that route is parity rather than divergence. Clients keying off ErrorCode must account for those cases; in particular, because 208 is a brand-new code rather than a reused one, a client that discriminates on ErrorCode must register 208 to recognise the transaction-not-found responses it previously saw as 203. A further exception is a malformed JSON request body, which the Java port answers uniformly with 203 (Incorrect request structure, HTTP 200) on every REST endpoint: this is parity for every endpoint that resolves the session first, and an intentional divergence on exactly two — DELETE /api/User/Ssp (.NET 204) and PUT /api/Provision (.NET HTTP 500). For endpoints ported from .NET, ErrorCode values remain identical to the .NET contract except the cases enumerated above — clients that key off ErrorCode require no changes elsewhere. Endpoints with no .NET counterpart (e.g. CreateGroupUpdate) define their own codes, documented per-endpoint on the Group update pages; these include codes absent from .NET such as 209.

7.1. TransactionId Validation

Endpoints that accept TransactionId reject non-positive values (0 and negative numbers) with a uniform rule across both REST and SOAP transports.

Affected endpoints:

  • POST /api/Task/Pending/Transaction / SOAP FTGetCPEPendingTasksByTransaction

  • POST /api/Parameter/Transaction / SOAP FTGetDeviceParametersByTransID

  • POST /api/Transaction / SOAP FTGetTransactionStatus

  • POST /api/Diagnostic (get-by-transaction) / SOAP FTGetDiagnosticByTransaction

Behavior:

  • TransactionId must be a positive integer (> 0).

  • Invalid values (0, negative numbers, or null where the field is required) are rejected before any database access.

  • Response carries the typed response body with default values (empty collections or zero counts), HTTP status 200, and the error fields set as below.

Table 1. Response fields for invalid TransactionId
Field Value

ErrorCode

203 (INCORRECT_REQUEST_STRUCTURE)

Message

TransactionId must be a positive number

Table 2. Message comparison: legacy .NET vs Java port
Endpoint Input .NET (REST / SOAP) Java (REST & SOAP)

Task/Pending/Transaction

-1 or 0

REST: TransactionId must be a positive number
SOAP: No pending task found

TransactionId must be a positive number

Parameter/Transaction

-1 or 0

REST: TransactionId must be a positive number
SOAP: TransactionId must be positive

TransactionId must be a positive number

Transaction

-1 or 0

REST: TransactionId must be a positive number
SOAP: Transid <value> doesn’t exist

TransactionId must be a positive number

Diagnostic (get-by-transaction)

-1, 0, or empty ""

REST: TransactionId must be a positive number
SOAP: Transaction <value> not found

TransactionId must be a positive number

Rationale: the .NET message variation is a historical artifact of the legacy codebase, not a documented contract. The Java port standardises on a single, unambiguous message while preserving the ErrorCode=203 contract that clients rely on. Related tickets: DEV-1714, DEV-1715, DEV-1717.

The Diagnostic get-by-transaction endpoint is aligned with the same rule (DEV-2491). Legacy .NET REST already guarded non-positive TransactionId with this exact message (DiagnosticController.cs); legacy .NET SOAP had no up-front guard, so transid ⇐ 0 fell through to an empty result and returned Transaction <value> not found (Diagnostic.cs). The Java port unifies both transports on TransactionId must be a positive number. In addition, an empty REST "TransactionId" previously crashed deserialization (HTTP 500, ErrorCode 204); it is now rejected as ErrorCode 203 before any database access, consistent with the sibling endpoints.

A non-numeric REST "TransactionId" (for example "AA981") is handled the same way (DEV-2501). Previously it crashed Jackson body deserialization and returned HTTP 500, ErrorCode 204; it is now leniently deserialized to null and rejected by the same guard as empty and non-positive input — HTTP 200, ErrorCode 203, TransactionId must be a positive number. This matches legacy .NET, where a value that cannot bind to the request’s long TransactionId property leaves it at its 0 default, which the TransactionId ⇐ 0 guard then rejects with the same message. The lenient deserializer is written against the Jackson 3 API (tools.jackson.*), which is what Spring Boot 4 uses for the REST message converters.

This currently applies to three of the required-TransactionId REST endpoints: POST /api/Diagnostic, POST /api/Transaction, and POST /api/Parameter/Transaction. The fourth required endpoint, POST /api/Task/Pending/Transaction, is not yet covered: its request field is inherited by the optional list endpoints (POST /api/Task/Pending, /Failed, /Rejected), whose legacy long? semantics treat a missing/non-numeric TransactionId as "operate on all". Unifying it therefore depends on an unresolved product decision and is deferred.

For a valid, positive TransactionId that does not correspond to any stored transaction, the Java port distinguishes a missing transaction from an existing transaction that simply has no tasks — on both REST and SOAP transports:

Legacy .NET SOAP (FTGetTransactionStatus) already drew this distinction; legacy .NET REST did not — it returned the no-tasks message for both cases. The Java port aligns both transports on the SOAP behavior. HTTP 200 is unchanged in every case. Since DEV-2484 the transaction-not-found case carries the new ErrorCode=208 (No transactions found) instead of 203; the "exists but no tasks" case keeps ErrorCode=203.

Table 3. Message comparison for a positive but non-existent TransactionId (endpoint Transaction / SOAP FTGetTransactionStatus)
Scenario .NET (REST / SOAP) Java (REST & SOAP)

Positive TransactionId, transaction row absent

REST: 203 / There are no tasks for Transid <value>
SOAP: 203 / Transid <value> doesn’t exist

208 / No transactions found (DEV-2484)

Positive TransactionId, transaction exists but no tasks

REST: 203 / There are no tasks for Transid <value>
SOAP: 203 / There are no tasks for Transid <value>

203 / There are no tasks for Transid <value>

Rationale: distinguishing "the transaction does not exist" from "the transaction has no tasks" gives callers an actionable, unambiguous message and makes REST consistent with SOAP. DEV-2484 (with PO sign-off) further standardises every transaction-not-found response onto the single new ErrorCode=208 (No transactions found); see Unified "No transactions found" Error (ErrorCode 208). Related tickets: DEV-2444, DEV-2484.

7.2. Absent or Empty Parameter Name

Several parameter-bearing operations reject a parameter that carries a Value but no Name (empty or null) with ErrorCode=203 (INCORRECT_REQUEST), whereas the legacy .NET API returns ErrorCode=204 (GENERAL) for the same input. The behavior is the same on REST and SOAP.

Table 4. Response comparison: legacy .NET vs Java port
Operation REST / SOAP .NET (REST & SOAP) Java (REST & SOAP)

Add object parameter

PUT /api/Object / FTAddObject

ErrorCode=204 (GENERAL)
Message=Parameter name is empty

ErrorCode=203 (INCORRECT_REQUEST)
Message=Parameter name is empty

Set parameters

PUT /api/Parameter / FTSetParameters

ErrorCode=204 (GENERAL)
Message=Parameter name is empty

ErrorCode=203 (INCORRECT_REQUEST)
Message=There is error at arraynames[0]:Parameter name is empty

Create diagnostic

POST /api/Diagnostic / FTCreateDiagnostic

ErrorCode=204 (GENERAL)
Message=Parameter name is empty

ErrorCode=203 (INCORRECT_REQUEST)
Message=Input parameter[0] name is empty.

Rationale: a parameter without a Name is a malformed request structure, so 203 (INCORRECT_REQUEST) is the semantically correct code. In the .NET implementation this scenario never reaches request-structure validation — the empty name is caught earlier, during payload-to-domain conversion (the ToDeviceParam conversion in the controller layer, before the business layer), as a generic exception that falls through to the catch-all 204 (GENERAL); the explicit 203 check inside the .NET business layer is unreachable dead code. The Java port keeps the more accurate 203. The Message wording differs across the three operations on the Java side (FTAddObject matches the .NET text exactly; FTSetParameters and FTCreateDiagnostic carry an operation-specific prefix) — only the ErrorCode is contractually relevant. Related tickets: DEV-2416 (FTAddObject), DEV-2458 (FTSetParameters; FTCreateDiagnostic identified during the same registry consolidation).

7.3. FTGetCPEByIP With a Malformed Stored STUN Address

GET /api/Device/ByIp / SOAP FTGetCPEByIP may, on a second STUN resolution attempt, read a device’s stored UDPConnectionRequestAddress from the database. When that stored value is malformed and cannot be parsed as a URI, the Java port returns ErrorCode=203 (INCORRECT_REQUEST) with the parser error message, whereas the legacy .NET API lets the raw UriFormatException fall through to the catch-all ErrorCode=204 (GENERAL). The behavior is the same on REST and SOAP.

Unlike the absent-parameter-name cases, the malformed value here originates from stored device data, not from a request field, so this path is only reachable for a device whose persisted STUN address is already corrupt.

Table 5. Response comparison: legacy .NET vs Java port
Scenario .NET (REST & SOAP) Java (REST & SOAP)

FTGetCPEByIP, malformed stored UDPConnectionRequestAddress (2nd STUN attempt)

ErrorCode=204 (GENERAL)

ErrorCode=203 (INCORRECT_REQUEST)

Rationale: an unparseable stored address is a structural data problem, so 203 (INCORRECT_REQUEST) is more precise than the generic 204 the .NET catch-all produces. Related ticket: DEV-2458 (identified during the ErrorCode-divergence registry consolidation).

7.4. Whitespace-Only Serial Number

Endpoints that require a device serial number reject a whitespace-only Sn (for example " ") as an incorrect request, whereas the legacy .NET API treats a whitespace string as a real serial and proceeds to a device lookup that then fails as "device not found".

Affected endpoints: every REST/SOAP operation that resolves a device by serial number. The serial constraint is uniform across the API (@NotBlank on the shared Device.Sn), so an absent, empty, or whitespace-only serial is always rejected before any database access. Pending-transactions retrieval (POST /api/Transaction/Task/Pending / SOAP FTGetCPEPendingTransactions) was the last endpoint brought into line with this rule.

Behavior:

  • An absent or empty Sn returns ErrorCode 203 on both transports — identical to .NET.

  • A whitespace-only Sn also returns ErrorCode 203 in the Java port, whereas .NET returns ErrorCode 201 (NO_CPE) because string.IsNullOrEmpty(" ") is false, so the value reaches the device lookup and misses.

  • Response carries the typed response body with default values, HTTP status 200, and the fields below.

Table 6. Response fields for a blank serial
Field Value

ErrorCode

203 (INCORRECT_REQUEST)

Message

Serial number is null or empty

Table 7. ErrorCode comparison: legacy .NET vs Java port
Input .NET (REST & SOAP) Java (REST & SOAP)

Absent or empty Sn

201 (NO_CPE)

203 (INCORRECT_REQUEST)
Message=Serial number is null or empty

Whitespace-only Sn (" ")

201 (NO_CPE) — Device with …​ not found

203 (INCORRECT_REQUEST)
Message=Serial number is null or empty

For an absent or empty serial the Java port already returned the correct 203 on most endpoints; the pending-transactions endpoints previously returned 201 (NO_CPE) for this case as well, which DEV-2454 corrected. The whitespace-only row is the part that intentionally diverges from .NET.

Rationale: a serial made only of whitespace is not a usable identifier, so treating it as a malformed request (203) is both more correct and keeps serial validation uniform across every serial-bearing endpoint. .NET’s acceptance of a whitespace serial is an artifact of string.IsNullOrEmpty. Related tickets: DEV-2422, DEV-2454.

7.5. FTGetUserDetailsFromDevice Device-Not-Found Message

When FTGetUserDetailsFromDevice receives a well-formed but non-existent devicesn, the device does not resolve to a CPE, so both transports return ErrorCode 201 (NO_CPE). The ErrorCode is identical to the .NET contract; only the Message text differs. In the .NET reference the message originates in the BLL device lookup (Device.FindBySerial), which throws a differently-worded FTExceptionNoCpe in each of the two separate .NET projects, so the wire text is not even consistent between .NET transports. The Java port uses a single shared service method for both transports and emits one uniform device-not-found message.

Affected endpoints:

  • SOAP FTGetUserDetailsFromDevice

  • REST POST /api/UserInfo (get user details by device serial)

Behavior:

  • Device existence is resolved against the CPE registry (by serial number), independently of whether a customer/user-info record exists.

  • A devicesn that resolves to no device returns ErrorCode 201 (NO_CPE), HTTP 200, with the user data fields nil.

  • A devicesn that resolves to more than one device returns ErrorCode 203 (INCORRECT_REQUEST_STRUCTURE) with the uniform message Must be only 1 CPE.

  • A devicesn that resolves to exactly one device but has no stored user info returns ErrorCode 100 (SUCCESS) with the user data fields nil (parity with the empty .NET User).

  • The Message is the standard Java device-not-found text used by every NO_CPE-bearing endpoint.

Table 8. Response fields for a non-existent devicesn
Field Value

ErrorCode

201 (NO_CPE)

Message

Device with Serial number = <sn> not found

Table 9. Message comparison: legacy .NET vs Java port
Input .NET Java (REST & SOAP)

Non-existent devicesn

SOAP: + Device with serial '<sn>' not found+
REST: + Device with Serial number '<sn>' not found+
(both ErrorCode 201, NO_CPE)

Device with Serial number = <sn> not found
(ErrorCode 201, NO_CPE)

Rationale: the contract-critical ErrorCode is preserved identically (201 / NO_CPE). A single shared Java service method (findUserInfoByDeviceSerial) serves both transports and therefore cannot reproduce the per-transport message wording of the two distinct .NET codebases (which differ from each other in casing, quoting, and a leading space). The Java port emits the project-wide device-not-found message (buildDeviceNotFoundMessage), keeping the message consistent with every other NO_CPE endpoint in the API. Related ticket: DEV-2423.

The same single-shared-service reasoning applies to the multiple-CPE case (ErrorCode 203): the .NET codebases word it differently per transport (SOAP Must be only 1 CPE, REST Must be only 1 Device). The Java port emits one uniform message, Must be only 1 CPE, on both transports. ErrorCode 203 is identical to .NET. Related ticket: DEV-2423.

7.6. Unified "No transactions found" Error (ErrorCode 208)

DEV-2484 (with PO sign-off) introduces a new, Java-only error code — ErrorCode=208 (NO_TRANSACTIONS), Message=No transactions found — and routes every "the requested transaction was not found" case onto it. The message is a fixed constant: it does not embed the TransactionId.

This is an intentional divergence from .NET. The legacy .NET API has no 208 code (its ResultXml result codes run 200207); it returned ErrorCode=203 for these transaction-not-found cases, with per-endpoint, per-transport message wording (Transid <value> doesn’t exist, Transaction <value> not found). The Java port replaces all of that with the single 208 / No transactions found. HTTP status remains 200 in every case (only the response-body ErrorCode and Message change), so this is not an HTTP-breaking change.

Endpoints unified onto ErrorCode=208 for a positive TransactionId that does not correspond to any stored transaction (REST and SOAP alike):

Endpoint (REST / SOAP) Not-found trigger Previous wording (superseded)

Task/Pending, Task/Pending/Transaction / FTGetCPEPendingTasks, FTGetCPEPendingTasksByTransaction

No pending task matches and the transaction exists in none of the four task tables

203 / Transid <value> doesn’t exist (DEV-2467)

Transaction / FTGetTransactionStatus

The transaction row is absent (countById ⇐ 0)

203 / Transid <value> doesn’t exist (DEV-2444)

Diagnostic (get-by-transaction) / FTGetDiagnostic

No diagnostic entries exist for the transaction

203 / Transaction <value> not found (DEV-2491)

The "transaction exists but simply has no tasks" case is deliberately excluded from this unification: Transaction / FTGetTransactionStatus keeps returning ErrorCode=203 / There are no tasks for Transid <value> in that case (see TransactionId Validation above). The 208 code means "no such transaction", not "no tasks".

Table 10. Response for a positive but non-existent TransactionId
Field Value

ErrorCode

208 (NO_TRANSACTIONS)

Message

No transactions found

HTTP status

200

Rationale: a single, unambiguous error code and message for "the transaction does not exist" is easier for integrators to detect programmatically than the previous mix of 203 messages that overlapped with unrelated INCORRECT_REQUEST cases, and dropping the echoed TransactionId from the message avoids leaking the queried id back on the wire. Related ticket: DEV-2484 (supersedes the wording of DEV-2444, DEV-2467, DEV-2491 on these paths).

7.7. Pending Tasks: Non-Existent TransactionId

Complements TransactionId Validation. That rule covers non-positive TransactionId values (rejected before any database access). This rule covers a positive but non-existent TransactionId on the pending-tasks retrieval path.

Affected endpoints (all four share a single service method, so the behaviour is identical across them):

  • POST /api/Task/Pending / SOAP FTGetCPEPendingTasks

  • POST /api/Task/Pending/Transaction / SOAP FTGetCPEPendingTasksByTransaction

Behavior:

  • When a positive TransactionId is supplied and no pending task matches, the service checks whether the transaction exists at all (any task in the pending, completed, failed, or rejected tables — mirroring the legacy .NET IsTransactionExists).

  • If the transaction does not exist, the response carries ErrorCode=208 (No transactions found) and Message=No transactions found, with the typed response body and an empty tasks list, HTTP status 200. Since DEV-2484 this uses the new unified ErrorCode=208 instead of the earlier 203 / Transid <value> doesn’t exist; see Unified "No transactions found" Error (ErrorCode 208).

  • If the transaction exists but simply has no pending tasks, the response is an empty-SUCCESS (ErrorCode=100) body — no error. On Task/Pending/Transaction / FTGetCPEPendingTasksByTransaction the Message field carries No pending task found in this case (DEV-2618); on Task/Pending / FTGetCPEPendingTasks the Message stays empty. See Pending Tasks by Transaction: "No pending task found" Message.

Field Value (positive, non-existent TransactionId)

ErrorCode

208 (No transactions found) — DEV-2484 (was 203 under DEV-2467)

Message

No transactions found

tasks

empty list

Supersedes the DEV-1931 decision for this path. DEV-1931 generalised the pending endpoints to always return an empty SUCCESS when no pending task matched (aligning them with Task/Failed and Task/Rejected); doing so suppressed the legacy error for a non-existent transaction. DEV-2467 restored an error for the positive-non-existent-transaction case only, while keeping DEV-1931’s valid part (no TransactionId, or an existing transaction with no pending tasks, still returns empty SUCCESS). DEV-2484 then replaced the 203 / Transid <value> doesn’t exist wording of that error with the unified ErrorCode=208 / No transactions found.

As a result, Task/Pending now diverges from Task/Failed and Task/Rejected: the failed/rejected path has no IsTransactionExists gate in .NET either (legacy GetTasksFromSql), so those endpoints keep returning empty SUCCESS for a non-existent transaction. This divergence is itself faithful to the legacy .NET behaviour. Related tickets: DEV-2467 (restores the error), DEV-2484 (unifies it on 208), DEV-1931 (superseded on this path).

7.8. Pending Tasks by Transaction: "No pending task found" Message

Complements Pending Tasks: Non-Existent TransactionId. That rule covers a positive but non-existent TransactionId (ErrorCode=208). This rule covers a positive TransactionId that exists but has no pending tasks, on the by-transaction retrieval path only.

Affected endpoints:

  • POST /api/Task/Pending/Transaction / SOAP FTGetCPEPendingTasksByTransaction

Behavior: when the transaction exists (it has at least one task in the pending, completed, failed, or rejected table) but no pending task matches, the response carries ErrorCode=100 (SUCCESS) and Message=No pending task found, with the typed response body and an empty tasks list, HTTP status 200.

Field .NET Java

ErrorCode

203 (INCORRECT_REQUEST)

100 (SUCCESS)

Message

No pending task found

No pending task found

tasks

empty list

empty list

HTTP status

200

200

In the legacy .NET API this endpoint resolves to the single-argument GetCPEPendingTasks(long transId) overload, whose getPendingQueueForDevice throws FTExceptionIncorrectRequestStructure("No pending task found")ErrorCode=203 — for any empty result, conflating "transaction not found" with "transaction found, no pending tasks". The Java port shares one service method between Task/Pending and Task/Pending/Transaction, and (per DEV-2484, PO sign-off) already keeps ErrorCode=100 for the "exists but no pending tasks" case rather than reverting to the .NET 203 conflation; DEV-2618 adopts only the .NET message text (No pending task found) for that case, so the code stays 100 while the message becomes actionable.

The plain Task/Pending / FTGetCPEPendingTasks endpoint is deliberately excluded: it keeps the empty Message (the generalised empty-SUCCESS from DEV-1931), matching Task/Failed and Task/Rejected. Only the by-transaction endpoint carries the message.

Rationale: ErrorCode=100 remains correct because the transaction was found, so clients keying off ErrorCode are unaffected; the added message gives integrators the same human-readable hint the legacy API produced without reintroducing the 203 conflation the 208 contract was designed to remove. Related tickets: DEV-2618 (this message), DEV-2484 (the 208 contract this builds on), DEV-2467, DEV-1931 (superseded on this path).

7.9. Diagnostic "Time in seconds" Metric

For a completed Get Diagnostic result (POST /api/Diagnostic / SOAP FTGetDiagnostic), the Java port enriches the parameter list with three calculated objects — Time in seconds, File size in MB, and Average speed in Mbps — computed from the device’s BOMTime/EOMTime timestamps and the transferred byte count. File size in MB and Average speed in Mbps are computed with the same formulas as the .NET reference and agree with it except at exact rounding midpoints (see the rounding note below); Time in seconds differs by definition.

Table 11. Time in seconds comparison: legacy .NET vs Java port
Value .NET (REST & SOAP) Java (REST & SOAP)

Definition

TimeSpan.Seconds — the seconds component of the interval (integer 059)

Total elapsed seconds of the interval (fractional)

Example: 10-second interval

10

10.0

Example: 31-microsecond interval (fast/local transfer)

0

0.000031

Rationale: the .NET TimeSpan.Seconds component discards whole minutes and all sub-second precision, so a 90-second diagnostic reports 30 and a sub-second diagnostic reports 0 — neither reflects the actual duration. The Java port reports the true total elapsed time. This divergence became observable only after DEV-2476: previously a sub-millisecond BOMTime/EOMTime interval was truncated to a millisecond-precision java.util.Date, so the three calculated objects were dropped entirely (see the ticket). DEV-2476 restores nanosecond-precision duration handling, which activates this code path and surfaces the intentional Time in seconds difference documented here. Related tickets: DEV-2476 (precision fix), BUG-DG-003 (the Time in seconds divergence).

Rounding mode. File size in MB and Average speed in Mbps use the same formulas as the .NET reference, but the Java port rounds to two decimals with RoundingMode.HALF_UP, whereas .NET Math.Round uses banker’s rounding (MidpointRounding.ToEven) by default. The two agree on every value except an exact rounding midpoint. For example, a 128 KB transfer gives File size in MB = 131072 / 1048576 = 0.125, which the Java port rounds up to 0.13 while .NET rounds to the even neighbour 0.12. This rounding-mode difference is pre-existing behaviour and was not introduced by DEV-2476. Related ticket: BUG-DG-004.

7.10. FindDeviceAdvanced Empty-Value Error Message

For POST /api/Device/FindDeviceAdvanced / SOAP FTFindDeviceAdvanced, when a parameter carries a valid Name but an empty Value (for example zip with ""), the Java port returns the error Message without the single trailing space that the legacy .NET API leaves on the assembled message. ErrorCode=203 (INCORRECT_REQUEST) is identical on both sides. The behavior is the same on REST and SOAP.

Table 12. Response comparison: legacy .NET vs Java port
Scenario .NET (REST & SOAP) Java (REST & SOAP)

FindDeviceAdvanced, valid key with empty value (for example zip="")

ErrorCode=203 + `Message=Parameter zip without value. ` (trailing space)

ErrorCode=203 + Message=Parameter zip without value. (no trailing space)

Rationale: the .NET implementation throws its StringBuilder output verbatim, keeping the trailing space produced by its per-segment format string. The Java port strips the single outer trailing space for a cleaner client-facing message; inter-segment spacing of multi-error messages is preserved. Only cosmetic whitespace differs — the wording and ErrorCode are unchanged. Note that the verbose Valid parameter names: …​ hint is appended only when a parameter name is invalid (matching the .NET isAnyInvalid gate), not on the empty-value path. Related ticket: DEV-2507.

7.11. Task StartTime Parsing (Date-Only, Offset, Malformed)

For the three optional StartTime task-list endpoints — POST /api/Task/Pending, POST /api/Task/Failed, POST /api/Task/Rejected (SOAP FTGetCPEPendingTasks / FTGetCPEFailedTasks / FTGetCPERejectedTasks) — the REST StartTime is now bound as a raw string and parsed in the controller, converging on the legacy .NET REST behaviour where the field is a string parsed with Convert.ToDateTime inside a try/catch. This aligns REST with the SOAP transport, which already parsed the raw XML string.

Table 13. StartTime handling (REST & SOAP)
Input .NET (REST) Java (REST & SOAP)

Date-only (2025-10-02)

Accepted, start of day

HTTP 200, ErrorCode 100, tasks filtered from start of day

ISO local / offset date-time

Accepted

HTTP 200, ErrorCode 100

Blank or absent

No date filter

HTTP 200, ErrorCode 100, no date filter

Malformed (2025-13-40, banana)

Empty result

HTTP 200, ErrorCode 204, parser Message, empty tasks list (fail-closed)

Previously a date-only or offset StartTime crashed the default ISO_LOCAL_DATE_TIME deserializer and returned HTTP 500 / ErrorCode 204 — the Jackson 2 @JsonDeserialize on the request field was silently ignored by the Spring Boot 4 Jackson 3 (tools.jackson) message converter. Malformed input is deliberately not coerced to null (which would silently drop the date filter and return an unfiltered list); it returns ErrorCode 204 with the parser message, fail-closed.

An StartTime carrying an explicit UTC offset is normalized to the ACS server-local wall-clock (the deploy timezone) before being stored against the naive created column, matching the legacy .NET writer’s server-local DateTime — the same storage basis as the device-activity window (DEV-2433). Related ticket: DEV-2488.

7.12. Device Not Unique — Message Form

When a device lookup that carries the Sn / Oui / ModelName discriminators matches more than one stored device, both the legacy .NET API and the Java port reject the request with ErrorCode 201 (NO_CPE). The ErrorCode is identical to .NET; only the Message text differs, and it structurally cannot be made identical.

Affected endpoints:

  • POST /api/Device/Activity (REST only — no SOAP operation exists)

  • POST /api/Device/History (REST only — no SOAP operation exists)

  • POST /api/Device/Info / SOAP FTGetDeviceInfo

Behavior:

  • A duplicate match is rejected before the ISP access check, matching the legacy load() ordering.

  • Response carries the typed response body, HTTP status 200, and the error fields set as below.

Table 14. Response fields for a non-unique device match
Field Value

ErrorCode

201 (NO_CPE)

Message

Device with Serial number = <Sn>[, OUI = <Oui>][, Model name = <ModelName>] not unique

Table 15. Message comparison: legacy .NET vs Java port
Input .NET (REST) Java (REST & SOAP)

Sn + Oui + ModelName

Device with ID=<id> Serial number = <db serial>, OUI = <db oui>, Model name = <db model> not unique

Device with Serial number = <Sn>, OUI = <Oui>, Model name = <ModelName> not unique

Sn + Oui

Device with ID=<id> Serial number = <db serial>, OUI = <db oui>, Model name = <db model> not unique

Device with Serial number = <Sn>, OUI = <Oui> not unique

Sn only

Device with ID=<id> Serial number = <db serial>, OUI = <db oui>, Model name = <db model> not unique

Device with Serial number = <Sn> not unique

Rationale: the .NET message is rendered from an already-loaded database row, so it carries an ID=<n> prefix and spells the serial, OUI, and model as stored — including a Model name segment even when the client never sent one. The Java port composes the message from the request values it actually received, so those database-side details are unavailable to it. The Oui is uppercased in the message, matching the .NET constructor which uppercases OUI before the message is rendered. Related ticket: DEV-2619.

7.13. Oui Matched Against Manufacturer Name

Device lookups that filter on Oui match the value against both the product class OUI and the manufacturer name, unconditionally.

Affected endpoints: every endpoint resolving a device by Sn + Oui + ModelName, including POST /api/Device/Activity, POST /api/Device/History, and POST /api/Device/Info / SOAP FTGetDeviceInfo.

Table 16. Behavior comparison
.NET Java

Matches the manufacturer name only when the OuiManufacturer setting is enabled (default: disabled); otherwise matches the product class OUI only.

Always matches either the product class OUI or the manufacturer name; there is no equivalent setting.

Consequence: a request whose Oui carries a manufacturer name rather than an OUI resolves a device in the Java port, whereas the .NET reference in its default configuration returns ErrorCode 201. The query lives in the shared api-orm library and is not overridable from this service, so closing the gap requires a change there. Related ticket: DEV-2619.

7.14. Absent UserInfo Block Accepted

A device-mutation request that omits the UserInfo block entirely but carries custom parameters is accepted and applied, answering ErrorCode 100 (SUCCESS). The legacy .NET REST API answers ErrorCode 204 (GENERAL) for the same input.

Affected endpoints:

  • PUT /api/UserInfo / SOAP FTUpdateDeviceInfo

  • POST /api/Device (device creation carrying only custom parameters) / SOAP FTCreateDevice

Behavior: the block is treated as present-but-empty. Every field falls back to its stored device value exactly as an explicitly empty block would, the custom parameters are written, and the response is a normal success. The all-input-null guard is unaffected: a request that omits UserInfo and carries no usable parameter is still rejected with ErrorCode 203 (INCORRECT_REQUEST, All input parameters are null), byte-identical to the .NET business layer.

Table 17. Behavior comparison
Input .NET REST .NET SOAP Java (REST & SOAP)

UserInfo absent, Parameters carries latitude

204 (GENERAL)

100 (SUCCESS)

100 (SUCCESS)

UserInfo absent, no usable parameter

204 (GENERAL)

203 (INCORRECT_REQUEST)

203 (INCORRECT_REQUEST)

Rationale: the .NET REST 204 is not a decision but an unhandled crash. UserInfoController.cs:45-57 dereferences request.UserInfo.Cust1 while assembling the custs map, before the business layer is reached at :60; with the block absent this raises a NullReferenceException, which ConvertApi.SetErrorCode maps through its final else branch to Result.GeneralError. The .NET SOAP transport cannot exhibit the fault at all, because FTUpdateDeviceInfo takes flat string arguments (ACSWS.cs:1780-1788) rather than a nullable object, and its business-layer guard (Device.cs:2127-2133) passes whenever custs is non-empty — so .NET SOAP already succeeds on the semantically equivalent input. The Java port therefore aligns REST with SOAP on both implementations rather than reproducing a null-dereference crash. Made reachable by DEV-2620, which bound the Parameters array for these bodies; before that change such a request was rejected by the all-input-null guard and never reached the dereference. Related ticket: DEV-2620.

7.15. Comma Decimal Separator Accepted for Coordinates

The latitude and longitude custom parameters accept both the comma and the dot as decimal separator, on every endpoint that writes a coordinate and on every endpoint that filters by one. The value is normalized to the dot form before it is stored or compared, so it is always read back with a dot and a comma-form filter selects exactly the same devices as its dot form.

Affected endpoints — write path:

  • PUT /api/UserInfo / SOAP FTUpdateDeviceInfo

  • POST /api/Device / SOAP FTCreateDevice

Affected endpoints — search path:

  • POST /api/Device/FindDeviceAdvanced / SOAP FTFindDeviceAdvanced

  • POST /api/Device/UserInfo / SOAP FTFindDevice

Behavior: a coordinate is validated after the separator is normalized, and the normalized form is what reaches storage or the SQL predicate. Because coordinates are persisted as a numeric column, GET /api/UserInfo / SOAP FTGetUserDetailsFromDevice returns the dot form regardless of which separator was submitted.

Table 18. Behavior comparison for latitude=50,5 — write path
Aspect .NET REST .NET SOAP Java (REST & SOAP)

Accepted

No — 203 (INCORRECT_REQUEST_STRUCTURE)

Yes — 100

Yes — 100

Stored form

n/a

50.5

50.5

Table 19. Behavior comparison for latitude=50,5 — search path
Endpoint .NET REST .NET SOAP Java (REST & SOAP)

FindDeviceAdvanced / FTFindDeviceAdvanced

No — 204 (GeneralError)

No — 204 (GeneralError)

Yes — 100, filter applied as latitude = 50.5

Device/UserInfo / FTFindDevice

No — 203 (IncorrectRequestStructure)

Yes — 100

Yes — 100, filter applied as latitude = 50.5

The .NET search path is not uniform, which is why the two rows differ. FindDeviceAdvanced validates parameter names only and lets the value reach the business layer, where Device.FindDeviceAdvanced (FT.FTACSWS.BLL/Device.cs:466-469) calls Gui.ConvertToDouble; that helper throws a plain Exception (FT.FTACSWS.BLL/Helper/FileName.cs:16-23), which both ConvertApi.SetErrorCode (ConvertApi.cs:218-250) and ConvertWSDL.SetErrorCode (ConvertWSDL.cs:236-268) map through their final else branch to Result.GeneralError. FTFindDevice and Device/UserInfo instead validate in getCusts before the business layer, and there the two transports differ: SOAP normalizes the separator in place (ACSWS.cs:1762) and therefore accepts the comma, while the REST copy has that same line commented out (DeviceController.cs:530) and rejects it with a typed FTExceptionIncorrectRequestStructure. The Java port applies one rule everywhere instead of reproducing that three-way split.

Rationale: the .NET reference is internally inconsistent here. On the write path, .NET SOAP normalizes in place before storing (ACSWS.cs:1763, item.Value = item.Value.Replace(",", "."), stored at :1766), whereas the equivalent .NET REST line is commented out (DeviceController.cs:531), leaving the value to be validated by Gui.doubleTryParse (FT.FTACSWS.BLL/Helper/FileName.cs:10-14), which parses with InvariantCulture and AllowDecimalPoint only and therefore rejects the comma. On the search path the same split reappears one layer down: FindDeviceAdvanced and FTFindDeviceAdvanced copy the submitted value verbatim into the filter map (DeviceController.cs:120-247, ACSWS.cs:671-791) and let Device.FindDeviceAdvanced convert it with Gui.ConvertToDouble (FileName.cs:16-23), which delegates to the same comma-rejecting doubleTryParse and throws on failure, so the whole request ends as 204; FTFindDevice and Device/UserInfo reuse their transport’s own getCusts copy and therefore inherit its normalize-or-not decision, giving 100 on SOAP and 203 on REST.

The Java port takes .NET SOAP’s write-path behavior and applies it uniformly to both transports and to both the write and the search path, giving one rule — accept either separator, always store, compare and return the dot form — instead of a per-transport, per-path split. On the search path this also corrects a silently wrong result set: the Java port previously dropped a comma-form coordinate filter without an error, returning 100 together with devices that do not match the requested coordinate. Normalizing makes the filter apply instead of vanish; for the comma form the ErrorCode is unchanged by this correction (100 before and after — only the returned device list changes). A genuinely unparsable coordinate is a different input class and is now rejected rather than dropped — see Unparsable Coordinate Rejected on the Search Path. The write-path normalization additionally closes a latent defect: the Java port previously validated the comma form (by replacing before parsing) but stored the original string, which then reached the ACS client’s Double.valueOf conversion and failed with ErrorCode 204. Related ticket: DEV-2620.

7.16. Unparsable Coordinate Rejected on the Search Path

A latitude or longitude search filter whose value is not a number in either separator form — latitude=abc — is rejected with ErrorCode 203 (INCORRECT_REQUEST_STRUCTURE, message Custom parameter latitude must be correct double). It is never silently dropped.

Affected endpoints:

  • POST /api/Device/FindDeviceAdvanced / SOAP FTFindDeviceAdvanced

  • POST /api/Device/UserInfo / SOAP FTFindDevice

An absent or empty value is not affected: it leaves the filter unapplied without an error, on both the search and the write path, and on both implementations.

Table 20. Behavior comparison for latitude=abc
Endpoint .NET REST .NET SOAP Java (REST & SOAP)

FindDeviceAdvanced / FTFindDeviceAdvanced

204 (GeneralError)

204 (GeneralError)

203divergence

Device/UserInfo / FTFindDevice

203 (IncorrectRequestStructure)

203 (IncorrectRequestStructure)

203 — parity

Rationale: the divergence is confined to FindDeviceAdvanced, and it exists because .NET is internally inconsistent about this exact input. On its FTFindDevice route .NET already answers 203, from a typed FTExceptionIncorrectRequestStructure raised in getCusts (ACSWS.cs:1763-1765, DeviceController.cs:531-533) before the business layer is reached. On its FindDeviceAdvanced route no such check exists, so the value reaches Device.FindDeviceAdvanced (Device.cs:466-469) and Gui.ConvertToDouble throws a plain Exception (FileName.cs:16-23) that both SetErrorCode mappers fold into the catch-all Result.GeneralError. The 204 is therefore an unhandled crash, not a decision — the same pattern already recorded for FTAddObject / FTSetParameters / FTCreateDiagnostic. The .NET write path also answers 203 for this input (DeviceController.cs:531-533), as does the Java write path (DeviceInfoService.getCusts). Rather than reproduce a split in which the same malformed coordinate yields 203 on three routes and 204 on a fourth, the Java port answers 203 everywhere.

The alternative — dropping the predicate, as the Java port did before this change — was rejected outright: it returns ErrorCode 100 alongside devices that do not match the requested coordinate, so the caller receives a wrong result set with no indication that its filter was discarded. Related ticket: DEV-2620.

7.17. Malformed JSON Body Rejected Uniformly (ErrorCode 203)

A request whose body is not valid JSON — a syntax error such as a bare unquoted token ({"EventTypeValueChange": Tru}) — is rejected uniformly on every REST endpoint with HTTP 200, ErrorCode 203 (INCORRECT_REQUEST_STRUCTURE), Message Incorrect request structure, and the endpoint’s typed response body (any collection serialized as an empty array). The body is not parseable, so no field binds and the request object is absent.

This mirrors the legacy .NET behavior for the overwhelming majority of endpoints: a malformed body makes the .NET model binder yield a null request, and the first action step — Session.CreateSession(request, …) (Session.cs:233-234) — throws FTExceptionIncorrectRequestStructure("Incorrect request structure"), which ConvertApi.SetErrorCode (ConvertApi.cs:237-238) maps to Result.IncorrectRequestStructure (203) at HTTP 200. For every endpoint that calls CreateSession first, the Java 203 is therefore parity, not a divergence. Related ticket: DEV-2623.

Two endpoints are an intentional divergence (PO sign-off, DEV-2623): the Java port’s uniform 203 differs from what .NET happens to return there, because .NET dereferences the null request before (or without) CreateSession and so does not reach the 203 path.

Endpoint (REST) .NET behavior on a malformed body Java port

PUT /api/Provision (Set)

HTTP 500Set evaluates request.Profile (ProvisionController.cs:21) before the try/CreateSession, so the null request throws an unhandled NullReferenceException

HTTP 200, ErrorCode 203divergence

DELETE /api/User/Ssp (DeleteSSP)

HTTP 200, ErrorCode 204 (GeneralError) — DeleteSSP dereferences request.Username (UserController.cs:80) inside its try with no CreateSession; the NullReferenceException is caught and SetErrorCode folds it into the catch-all 204

HTTP 200, ErrorCode 203divergence

The divergence is deliberate: a single, structurally correct code for "the body could not be read" is more useful to integrators than reproducing .NET’s two accidental outcomes (an unhandled 500 on one endpoint, the catch-all 204 on another), both of which are crashes rather than decisions. Clients keying off ErrorCode on these two endpoints must accept 203 where they previously saw a 500-level failure or 204.

8. API Documentation

The API is fully documented using OpenAPI 3.0 (Swagger):

  • Swagger UI: Available at http://[server]/iot-webservice/swagger-ui/index.html

  • OpenAPI JSON: Available at http://[server]/iot-webservice/v3/api-docs

  • OpenAPI YAML: Available at http://[server]/iot-webservice/v3/api-docs.yaml

The OpenAPI documentation is generated automatically from annotations in the controller classes, ensuring that it’s always up-to-date with the actual implementation. The Swagger UI provides a user-friendly interface for exploring and testing the API.

9. External Integrations

The ACS integration flow follows this general pattern:

Diagram

9.1. ACS Client Configuration

The ACS client is configured in the application:

acs:
  url: ${ACS_URL}
  port: ${ACS_PORT}
  username: ${ACS_USERNAME}
  password: ${ACS_PASSWORD}

9.2. ACS Integration Endpoints

The application communicates with the Auto Configuration Server (ACS) system through SOAP API calls to manage and provision devices. The integration is implemented via the FtAcsApiClient class which encapsulates all ACS-related operations.

9.3. Device Management Endpoints

Operation Method Description

Create Device

createDevice()

Creates a new device in the ACS with specified manufacturer, model, OUI, protocol type, and serial number

Create Non-TR Device

createNonTRDevice()

Creates a non-TR-069 compliant device with custom configuration URL, login, and password

Clear Account Info

clearAccountInfo()

Clears the account information associated with a specified device serial number

Update User Info

updateUserInfo()

Updates detailed user information including custom fields, login credentials, geographic data, and contact details

Add Device To Blacklist

addDeviceToBlacklist()

Adds devices to a blacklist based on serial numbers, manufacturer, and model

Delete Device From Blacklist

deleteDeviceFromBlacklist()

Removes devices from a blacklist based on serial numbers, manufacturer, and model

9.4. Device Configuration Endpoints

Operation Method Description

Set Device Parameters

setDeviceParameters()

Sets or updates parameter values on devices with options for prioritization, grouping, and provisioning

Set Parameters Without Task

setDeviceParametersProvisionWithoutTask()

Sets device parameters without creating a task for immediate application

Get Parameter Data

getParameterDataListFromCPE()

Retrieves parameter data from devices with options to specify which aspects to fetch (names, values, attributes)

Add CPE Object

addCPEObject()

Creates a new object in the device’s data model with specified parameters

Delete CPE Object

deleteCPEObject()

Removes an object from the device’s data model

9.5. Device Operations Endpoints

Operation Method Description

Reboot CPE

rebootCPE()

Issues a reboot command to devices with options for prioritization and pushing

Reset CPE

resetCPE()

Issues a factory reset command to devices

Reprovision CPE

reprovisionCpe()

Triggers reprovisioning of devices with options for customizing what aspects to reprovision

Invoke RPC Method

invokeRPCMethod()

Invokes a custom RPC method on devices with specified content

Invoke RPC Without Task

invokeRPCMethodWithoutTask()

Invokes a custom RPC method without creating a task for immediate execution

Invoke Method

invokeMethod()

Invokes a system-defined method on devices with parameter values

Push Device

pushDevice()

Initiates a connection request to a device with specified timeout

9.6. File Management Endpoints

Operation Method Description

Download Files

downloadFiles()

Initiates file downloads to devices with options for authentication, delivery method, and post-download actions

Upload Files

uploadFiles()

Initiates file uploads from devices to a specified URL

Backup

backup()

Creates a device backup and uploads it to a specified location

Restore

restore()

Downloads and applies a backup to restore device configuration

9.7. Group Update Endpoints

Group Update orchestrates the same set of tasks across many devices grouped by manufacturer/model and selected by All, an existing Condition filter view, or Individual serial numbers. The endpoints are documented in detail (request/response fields, behavior, errors and examples) on a dedicated page: Group Update API.

Operation Path Description

Create Group Update

PUT /api/CreateGroupUpdate

Create a new group update campaign

Delete Group Update

PUT /api/DeleteGroupUpdate

Delete an existing group update campaign

Create Condition

PUT /api/CreateGroupUpdateCondition

Create a device filter condition for group updates

Delete Condition

PUT /api/DeleteGroupUpdateCondition

Delete a device filter condition

View Group Update List

POST /api/ViewGroupUpdateList

List all group update tasks

View Condition List

POST /api/ViewGroupConditionList

List predefined conditions

View Group Update Details

POST /api/ViewGroupUpdateDetails

Retrieve details of a specific group update

View Condition Details

POST /api/ViewGroupUpdateConditionDetails

Retrieve details of a specific condition

Activate Group Update

PUT /api/GroupUpdateActivate

Start execution of a group update

Pause Group Update

PUT /api/GroupUpdatePause

Pause an active group update

Stop Group Update

PUT /api/GroupUpdateStop

Stop a group update completely

9.8. Diagnostics Endpoints

Operation Method Description

Create Diagnostic

createDiagnostic()

Create a diagnostic session with input parameters and output parameter names to collect

9.9. Error Handling

ACS operations are wrapped in error handling that transforms exceptions into consistent API error responses with appropriate codes:

  • Success operations return transaction IDs where applicable

  • Errors are logged and wrapped in FtApiException with appropriate error codes

  • Connection issues to ACS result in NO_ACS error codes

10. Security Considerations

  • HTTPS: All API communications are encrypted using TLS

  • JWT Authentication: Ensures only authorized clients can access protected resources

  • Input Validation: All inputs are validated to prevent injection attacks