Skip to content

feat!: modernize SDK against current OpenAPI spec (knock-node parity) - #40

Merged
cjbell merged 19 commits into
mainfrom
cjbell-openapi-modernization-impl-b521
Sep 30, 2026
Merged

cjbell merged 19 commits into
mainfrom
cjbell-openapi-modernization-impl-b521

Conversation

@cjbell

@cjbell cjbell commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

This implements the plan in #39. It brings the Java SDK in line with the current OpenAPI spec and with knock-node. It fixes three bugs that made existing calls fail or return nothing, adds 9 endpoints (89 → 98), adds the fields that were missing, and ports Node's user-token signing. MIGRATION.md lists every breaking change.

Release

This ships as 2.0.0. Several changes break source or binary compatibility for code built against 1.x. For example, the 204 methods changing from String to void causes NoSuchMethodError in already-compiled callers.

  • The last commit carries a Release-As: 2.0.0 footer and a BREAKING CHANGE: footer. The PR title is marked feat!. Release-please will propose 2.0.0 whether this is merged or squashed.
  • The version in build.gradle.kts is still 1.0.0. Release-please bumps it in its release PR.

Bug fixes

  • Message-family pages were always empty. Message lists, message events, delivery logs, and user and object message lists now parse the items envelope. The accessor changes from entries() to items().
  • Object-array query params returned 422. Queries now use the indexed form (objects[0][id]=…) that the API expects, via app.knock.api.lib.QueryArrays. UserListSubscriptionsParams.objects, ObjectListSubscriptionsParams.recipients, and ScheduleListParams.recipients now take RecipientReference.
  • Bulk add subscriptions now requires id on each item, as the API does.
  • Preference center row types. The API returns workflows and categories, which the spec doesn't list. They're now known enum values.

New endpoints

  • workflowRecipientRuns(): list and get
  • users().preferenceCenter(): getConfig and generateSignedUrl
  • users().unsetPreferences and objects().unsetPreferences
  • objects().bulk().deleteSubscriptions
  • users().guides().resetGuideEngagements and unarchiveGuideMessage

Breaking changes

  • 204 endpoints return nothing. This covers delete on users, objects, and tenants; unsetChannelData; audience addMembers and removeMembers; and workflows().cancel. The async variants return CompletableFuture<Void?>.
  • Guide mark-as methods. They target PUT /v1/users/{user_id}/guides/messages/{action} and drop messageId. GuideActionResponse replaces the three per-action responses. GuideGetChannelResponse now has entries, guide_groups, guide_group_display_logs, and ineligible_guides.
  • messages().activities() removed. It duplicated messages().listActivities().
  • ScheduleCreateParams.repeats() now returns an Optional.

New fields

  • Workflow trigger: settings (sandbox_mode, skip_delay).
  • Schedule create: actor.
  • Tenants: name and resolve_full_preference_settings.
  • Audience add members: create_audience.
  • Feed listItems: locale, exclude, mode, and inserted_at.{gt,gte,lt,lte}.
  • Inline identify user: avatar, locale, and phone_number.
  • Message.source: step_ref, type, workflow_run_id, and workflow_recipient_run_id.
  • Slack, MS Teams, and Discord connections: knock_tenant_id. Slack token connections also get channel_name.

User token signing

app.knock.api.lib.UserTokens provides signUserToken and buildUserTokenGrant, with Grant, TokenEntity, and SignUserTokenOptions.

  • It uses JDK crypto only and stays Java 8 compatible.
  • Java-signed tokens are byte-identical to Node's for the same key, time, and grants.
  • It accepts PKCS#8 or PKCS#1 PEMs, raw or base64-encoded, and falls back to KNOCK_SIGNING_KEY.
  • The README documents it, and knock-java-example now has a runnable Main.

Findings for the API and spec

  • Node's inserted_at filter is silently ignored. Node sends inserted_at[gt], which the API ignores; the API only honors inserted_at.gt, the form in the spec. Java uses the spec form.
  • Guide {message_id} paths. The spec still lists guide paths with {message_id} alongside the path-less ones.
  • Preference center row types. The spec lists singular row types, but the API returns plural ones.

Not in this PR

These items from plan §6 are deferred:

  • Splitting the push and OneSignal channel-data unions.
  • The Slack incoming webhook url → incoming_webhook.url change. This needs confirmation that the API actually changed.
  • Removing TenantSetParams.preferences.
  • The MessageContents and AudienceMemberRequest renames.
  • Preference-set additions: channels, commercial_subscribed, and the persistence strategy.
  • Message.channel and recipient_snapshot.
  • Inline object name.

I also didn't add standalone Guide*Request models, because Node doesn't use them. The params classes carry those fields.

Most of this is hand-written into Stainless-generated files. The spec and Stainless config need matching updates, or the next regeneration will overwrite these changes.

Testing

  • ./scripts/lint passes.

  • ./scripts/test (Prism mock) passes: 1049 tests with 0 failures. The 198 skipped are the generated service tests, which are always disabled.

  • A read-only live smoke test against a real account, with strict response validation, passed for:

    • workflow recipient runs: list with filters and an object recipient, and get
    • preferenceCenter().getConfig
    • messages().list, including the new source fields
    • feed listItems with mode, exclude, and inserted_at
    • tenants().get with resolve_full_preference_settings

    It isn't committed.

Open in Web Open in Cursor 

cursoragent and others added 18 commits September 30, 2026 04:18
…y log lists

The API returns { items, page_info } for these endpoints, so the pages
were always empty. Matches knock-node's ItemsCursor.

Co-authored-by: Chris Bell <chris@cjbell.co>
The API rejects objects[][id]=... with a 422. Arrays containing objects now
use objects[0][id]=..., scalar arrays keep the key[]= form. User
subscription objects and schedule recipients accept RecipientReference.

Co-authored-by: Chris Bell <chris@cjbell.co>
Co-authored-by: Chris Bell <chris@cjbell.co>
Adds DELETE /v1/users/{user_id}/preferences/{id} and
DELETE /v1/objects/{collection}/{object_id}/preferences/{id}, matching
knock-node. Both return nothing; errors surface eagerly via a new
emptyHandler.

Co-authored-by: Chris Bell <chris@cjbell.co>
Co-authored-by: Chris Bell <chris@cjbell.co>
Co-authored-by: Chris Bell <chris@cjbell.co>
… add reset/unarchive

- markMessageAs{Seen,Interacted,Archived} now target PUT /v1/users/{user_id}/guides/messages/{action}
  and drop the messageId path parameter; request bodies follow the per-action schemas
- add resetGuideEngagements and unarchiveGuideMessage
- replace the three GuideMarkMessageAs*Response classes with GuideActionResponse
- reshape GuideGetChannelResponse to entries/guide_groups/guide_group_display_logs/ineligible_guides

Co-authored-by: Chris Bell <chris@cjbell.co>
…SignedUrl

Co-authored-by: Chris Bell <chris@cjbell.co>
Co-authored-by: Chris Bell <chris@cjbell.co>
messages().listActivities() already covers GET /v1/messages/{message_id}/activities.

Co-authored-by: Chris Bell <chris@cjbell.co>
users/objects/tenants delete, users/objects unsetChannelData, audiences
addMembers/removeMembers, and workflows cancel no longer return a String.
Raw responses are plain HttpResponse, matching unsetPreferences.

Co-authored-by: Chris Bell <chris@cjbell.co>
… listItems

inserted_at is serialized as inserted_at.gt etc. per the spec; the API
ignores the bracketed inserted_at[gt] form.

Co-authored-by: Chris Bell <chris@cjbell.co>
…eate_audience to audiences addMembers

Co-authored-by: Chris Bell <chris@cjbell.co>
…ine identify fields

- workflows().trigger(): settings (sandbox_mode, skip_delay)
- schedules().create(): actor; repeats is now optional
- tenants().set(): name and resolve_full_preference_settings; TenantRequest.name
- InlineIdentifyUserRequest: avatar, locale, phone_number

Co-authored-by: Chris Bell <chris@cjbell.co>
- Message.Source: step_ref, type, workflow_run_id, workflow_recipient_run_id
- Slack, MS Teams, and Discord connections: knock_tenant_id
- SlackTokenConnection: channel_name

Co-authored-by: Chris Bell <chris@cjbell.co>
…rTokenGrant)

Port of the Node SDK's signUserToken/buildUserTokenGrant using JDK crypto only.
Tokens are byte-identical to the Node SDK's for the same key, time, and grants.
Accepts PKCS#8 or PKCS#1 PEMs, raw or base64-encoded, and falls back to
KNOCK_SIGNING_KEY.

Co-authored-by: Chris Bell <chris@cjbell.co>
- MIGRATION.md covers the breaking changes on this branch
- README documents UserTokens.signUserToken
- knock-java-example gains a Main that triggers in sandbox mode, lists
  workflow recipient runs, and signs a user token
- .stats.yml: 98 configured endpoints

Co-authored-by: Chris Bell <chris@cjbell.co>
@cjbell cjbell changed the title feat: modernize SDK against current OpenAPI spec (knock-node parity) feat: bring SDK to current OpenAPI spec Sep 30, 2026
BREAKING CHANGE: 204 endpoints return nothing, guide message actions drop
messageId, messages().activities() is removed, message-family pages expose
items() instead of entries(), recipient filters take RecipientReference, and
ScheduleCreateParams.repeats() is optional. See MIGRATION.md.

Release-As: 2.0.0

Co-authored-by: Chris Bell <chris@cjbell.co>
@cursor cursor Bot changed the title feat: bring SDK to current OpenAPI spec feat!: modernize SDK against current OpenAPI spec (knock-node parity) Sep 30, 2026
@cjbell
cjbell added this pull request to stack #42 September 30, 2026 15:19
@cjbell
cjbell marked this pull request as ready for review September 30, 2026 15:45
@cjbell
cjbell requested a review from a team as a code owner September 30, 2026 15:45
@cjbell
cjbell requested review from MikeCarbone and meryldakin and removed request for a team September 30, 2026 15:45

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not approved: this is a breaking SDK modernization, not an auto-release PR, so it needs human review even though lint and test are green. No reviewers were assigned because two are already requested.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@cjbell
cjbell merged commit d3d321d into main Sep 30, 2026
4 checks passed
@cjbell cjbell mentioned this pull request Sep 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants