Skip to content

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.get SearchField — 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), and OrderField / OrderType are validated against an allowlist. An OrderField that is not a physical subscriber column (or a CustomField<n>) now falls back to EmailAddress ordering 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 saveJourneys::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 the UPDATE by (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 failed UPDATE also 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

  1. Do you call subscribers.get with a SearchField you 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 returns Success: false / ErrorCode: 4 instead 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.
  2. Do you treat TotalSubscribers: 0 from a subscribers.get search as "empty"? Add a Success check — Success: true with TotalSubscribers: 0 reliably means the search matched nothing; Success: false / ErrorCode: 4 means the field was invalid.
  3. Do you sort subscribers.search results by a non-standard OrderField? A value that is not a physical column or CustomField<n> now falls back to EmailAddress ordering silently (HTTP 200). Verify your sort field if results come back in an unexpected order.
  4. Do you assert on an empty result from an email address contains search? Some terms that previously returned nothing now correctly return matches. Check any saved search, segment or test that depended on the empty result.
  5. 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 about EMAILGATEWAY_SENDEMAIL_FULL_LIST if you need the full list reached.

Any questions? Contact us.