feat!: modernize SDK against current OpenAPI spec (knock-node parity) - #40
Merged
Merged
Conversation
…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>
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>
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>
cjbell
added this pull request to stack #42
September 30, 2026 15:19
cjbell
marked this pull request as ready for review
September 30, 2026 15:45
cjbell
requested review from
MikeCarbone and
meryldakin
and removed request for
a team
September 30, 2026 15:45
meryldakin
approved these changes
Sep 30, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


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.mdlists 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
Stringto void causesNoSuchMethodErrorin already-compiled callers.Release-As: 2.0.0footer and aBREAKING CHANGE:footer. The PR title is markedfeat!. Release-please will propose 2.0.0 whether this is merged or squashed.build.gradle.ktsis still 1.0.0. Release-please bumps it in its release PR.Bug fixes
itemsenvelope. The accessor changes fromentries()toitems().objects[0][id]=…) that the API expects, viaapp.knock.api.lib.QueryArrays.UserListSubscriptionsParams.objects,ObjectListSubscriptionsParams.recipients, andScheduleListParams.recipientsnow takeRecipientReference.idon each item, as the API does.workflowsandcategories, which the spec doesn't list. They're now known enum values.New endpoints
workflowRecipientRuns():listandgetusers().preferenceCenter():getConfigandgenerateSignedUrlusers().unsetPreferencesandobjects().unsetPreferencesobjects().bulk().deleteSubscriptionsusers().guides().resetGuideEngagementsandunarchiveGuideMessageBreaking changes
deleteon users, objects, and tenants;unsetChannelData; audienceaddMembersandremoveMembers; andworkflows().cancel. The async variants returnCompletableFuture<Void?>.PUT /v1/users/{user_id}/guides/messages/{action}and dropmessageId.GuideActionResponsereplaces the three per-action responses.GuideGetChannelResponsenow hasentries,guide_groups,guide_group_display_logs, andineligible_guides.messages().activities()removed. It duplicatedmessages().listActivities().ScheduleCreateParams.repeats()now returns anOptional.New fields
settings(sandbox_mode,skip_delay).actor.nameandresolve_full_preference_settings.create_audience.listItems:locale,exclude,mode, andinserted_at.{gt,gte,lt,lte}.avatar,locale, andphone_number.Message.source:step_ref,type,workflow_run_id, andworkflow_recipient_run_id.knock_tenant_id. Slack token connections also getchannel_name.User token signing
app.knock.api.lib.UserTokensprovidessignUserTokenandbuildUserTokenGrant, withGrant,TokenEntity, andSignUserTokenOptions.KNOCK_SIGNING_KEY.knock-java-examplenow has a runnableMain.Findings for the API and spec
inserted_atfilter is silently ignored. Node sendsinserted_at[gt], which the API ignores; the API only honorsinserted_at.gt, the form in the spec. Java uses the spec form.{message_id}paths. The spec still lists guide paths with{message_id}alongside the path-less ones.Not in this PR
These items from plan §6 are deferred:
url→incoming_webhook.urlchange. This needs confirmation that the API actually changed.TenantSetParams.preferences.MessageContentsandAudienceMemberRequestrenames.channels,commercial_subscribed, and the persistence strategy.Message.channelandrecipient_snapshot.name.I also didn't add standalone
Guide*Requestmodels, 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/lintpasses../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:
preferenceCenter().getConfigmessages().list, including the new source fieldslistItemswithmode,exclude, andinserted_attenants().getwithresolve_full_preference_settingsIt isn't committed.