API Behavior Changes in v5.9.4
Octeth v5.9.4 closes a set of injection surfaces in the subscriber APIs and removes several silent failures. Every change on this page was deliberate.
None of them alter a successful, well-formed request. One turns a class of malformed input that used to appear to succeed into an explicit error — which is exactly the kind of thing an integration can be quietly relying on.
Before upgrading, read Tier 1. If you drive subscribers.get search with a field value you build or forward from another system, it is worth a few minutes of your time.
Finalized for the v5.9.4 release
This page was maintained through the release cycle and finalized when v5.9.4 shipped on 14 August 2026. It is the complete list for this version.
One general note on error codes
ErrorCode and ErrorText are arrays, not scalars, on most endpoints. A rejection typically returns "ErrorCode": [13], not "ErrorCode": 13. Code written as if (response.ErrorCode === 13) will not match — use response.ErrorCode.includes(13) or your language's equivalent.
Note the exception below: subscribers.get returns a scalar ErrorCode (e.g. "ErrorCode": 4), consistent with its existing codes 1, 2, 3. Match it as a scalar.
Tier 1 — Calls that used to succeed now return an error
subscribers.get — an invalid SearchField is now rejected
SearchField was previously forwarded unvalidated. A value that was neither a default search field nor a real custom field simply produced an internal unknown column error that surfaced as an empty success — Success: true with Subscribers: false and TotalSubscribers: 0, indistinguishable from a search that genuinely matched nothing.
It is now validated. A legitimate SearchField is either a default search field — EmailAddress, SubscriberID, SubscriptionDate, SubscriptionIP, OptInDate — or the numeric ID of a custom field. Anything else is rejected with Success: false and the new ErrorCode: 4 ("Invalid search field").
This closes a SQL-injection surface: the rejected values are exactly the ones that could smuggle SQL through the field name. A successful, well-formed search — a real field name or a numeric custom-field ID, which is every documented use — is unchanged. Only calls that were already failing (a typo'd field, a custom field passed by name instead of numeric ID, or an injection attempt) change, from a masked empty success to an explicit ErrorCode: 4.
If you treat TotalSubscribers: 0 from a search as "no matches", add a Success check: a well-formed search that matches nothing still returns Success: true, while an invalid SearchField now returns Success: false / ErrorCode: 4.
Tier 2 — Same call, different results
Email address "contains" search returns matches it previously missed
A contains search on an email address is served by a full-text index. Search terms whose words collide with the database's built-in stopword list produced a query that matched nothing, so the search returned zero rows for terms that plainly should have matched. Those tokens are now dropped from the generated query rather than poisoning it.
No request changes. If you have a saved search, a segment, or a test that asserted an empty result for one of these terms, it will now return rows — which is the correct answer.
emailgateway.sendemail list sends can now reach the whole list
A list send has historically delivered to the first 250 recipients and returned a success response, with nothing in the response indicating the list was truncated. That remains the default in v5.9.4, so no existing integration changes.
A new server-side setting, EMAILGATEWAY_SENDEMAIL_FULL_LIST, makes a list send paginate the entire list. If you send to lists larger than 250 through the Email Gateway, ask your administrator to enable it — and be aware that doing so genuinely increases the number of emails a single call sends.
Tier 3 — Security closures
These only affect callers doing something that was never intended to work. Listed for completeness and for anyone auditing.
subscribers.getSearchField— SQL injection through the criteria builder's field name closed (the caller-visible effect is the Tier 1 entry above). The value half of a search was always parameter-escaped; the field-name half is now identifier-escaped too.subscribers.search— the internal segment-engine call is now parameter-safe (a value can no longer smuggle an additional request parameter), andOrderField/OrderTypeare validated against an allowlist. AnOrderFieldthat is not a physical subscriber column (or aCustomField<n>) now falls back toEmailAddressordering silently, with HTTP 200, where an unusual value could previously reach the query builder. A successful sort on a real column is unchanged. If your results come back in an unexpected order after upgrading, your sort parameter is being rejected — it is not reported as an error.- Journey action save —
Journeys::CreateAction()'s update path now verifies the target action row exists and belongs to both the requesting account and the journey being edited before writing, and scopes theUPDATEby(ActionID, RelUserID, RelJourneyID). This closes a cross-tenant write where a crafted save could re-parent another account's action row into the caller's journey, and stops a stale/wrong action id from silently succeeding (which orphaned child actions into unreachable branches). A legitimate journey save — which only ever targets rows the account already owns in that journey — is unchanged; only stale/foreign ids are now rejected. A failedUPDATEalso returns an error instead of a fabricated success. - Segment rules — unresolvable operator fails closed — a suppression rule whose operator is missing or unrecognised now compiles to an unsatisfiable predicate (matches nobody) and is logged, instead of silently inverting to "not suppressed". The old inversion could include suppressed addresses in an audience. Normal segments built in the UI always carry a valid operator and are unaffected; this only changes malformed/hand-crafted rules.
Upgrade checklist
- Do you call
subscribers.getwith aSearchFieldyou build or forward from another system? Make sure it is a default search field (EmailAddress,SubscriberID,SubscriptionDate,SubscriptionIP,OptInDate) or a numeric custom-field ID. Anything else now returnsSuccess: false/ErrorCode: 4instead of a masked empty success. In particular, if you pass a custom field by its name rather than its numeric ID, that call was already returning nothing and will now return an explicit error. - Do you treat
TotalSubscribers: 0from asubscribers.getsearch as "empty"? Add aSuccesscheck —Success: truewithTotalSubscribers: 0reliably means the search matched nothing;Success: false/ErrorCode: 4means the field was invalid. - Do you sort
subscribers.searchresults by a non-standardOrderField? A value that is not a physical column orCustomField<n>now falls back toEmailAddressordering silently (HTTP 200). Verify your sort field if results come back in an unexpected order. - Do you assert on an empty result from an email address
containssearch? Some terms that previously returned nothing now correctly return matches. Check any saved search, segment or test that depended on the empty result. - Do you send to lists larger than 250 recipients via
emailgateway.sendemail? Those sends have always stopped at 250 while reporting success. The default is unchanged in v5.9.4 — talk to your administrator aboutEMAILGATEWAY_SENDEMAIL_FULL_LISTif you need the full list reached.

Help Portal