Skip to content

Align Knowledge endpoints in 2.17 and Preview with API responses - #762

Merged
cathalging merged 2 commits into
mainfrom
cathalging/b3-knowledge
Oct 23, 2026
Merged

cathalging merged 2 commits into
mainfrom
cathalging/b3-knowledge

Conversation

@cathalging

@cathalging cathalging commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Why?

The Knowledge operations (internal articles, folders, content snippets, content search and bulk actions) document errors the API never returns, leave out ones it does, and don't say which OAuth scope each one needs. Several examples also show fields or values the API doesn't send, so integrators build against the wrong contract.

How?

Each documented behaviour was checked against what the API returns, and the scopes, error responses, parameters and examples were corrected in both the 2.17 and Preview specs. No version after 2.17 changes these operations, so both get the same edits.

Risks

The API doesn't change, but these changes affect generated types: the Preview SDK when it's next regenerated, and any client generated from 2.17 once it's published.

Change Could SDK code stop compiling? Did that code work before?
Bulk actions (2.17 and Preview) and content search (Preview) lose their 403 response Yes, where code references that error type No. A token missing the scope gets a 401 token_unauthorized, never a 403
Content search pages.next and pages.prev are no longer nullable Possibly, where code compares them to null No. The API leaves them out on the first and last page instead of sending null
Preview gains optional page and per_page (list and search internal articles), admin_id (detach a tag from an internal article or content snippet), and json_blocks and locale (create and update internal article) No —

Behaviour that looks like a bug, documented as it is

Operation What the docs now say
listFolders Null description, emoji and parent_folder_id are left out of list items. Retrieve, create and update return them as null
listInternalArticles, listFolders pages.page and pages.per_page come back as strings when you send them
Content snippet operations json_blocks comes back as a JSON-encoded string, not an array
detachTagFromInternalArticle, detachTagFromContentSnippet admin_id is checked, and an unknown teammate is a 404, but the removal isn't recorded against them
updateInternalArticle An unknown author_id is a 404 with code parameter_invalid
Internal article and folder errors request_id is null

Generated with Claude Code

@markdennis22 markdennis22 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for this, Cathal.
I compared both specs before and after the change, checked every reference resolves, ran fern check (0 errors and 317 warnings, the same as main), and checked the new statements against what the API returns.

A few questions:

  1. tag.applied_at / applied_by after a removal: The new text says both are null "when the tag has just been removed". That's true when you detach a tag from an article, internal article or content snippet. Detaching from a contact, conversation or ticket returns the removed tag's original applied_at and applied_by, which is what the detachTagFromContact, detachTagFromConversation and detachTagFromTicket examples already show. Could the null case name the content operations only? The "returned when" list also leaves out listing a contact's tags and external pages.

  2. Detach admin_id: The description says it's "the teammate to attribute the removal to", but the removal isn't recorded against anyone. The id is only checked, and an unknown teammate returns 404 admin_not_found. Should detachTagFromInternalArticle and detachTagFromContentSnippet say that instead?

  3. Folder list null keys: The reference showed parent_folder_id: null on list items. The API leaves out description, emoji and parent_folder_id from list items when they're null, but includes them as null when you retrieve, create or update a folder. Should the list example keep the null keys until the list behaves the same way, or describe the omission?

  4. List internal articles pages: With the new page and per_page params, the list response echoes them back as strings, for example "per_page": "1". Search returns integers. Should pages.page and pages.per_page stay integer, or say they come back as strings when you send them?

  5. Content search 401: Content search keeps the generic Unauthorized response in both versions, while bulk actions and the snippet operations now name their scope and show the token_unauthorized example. A token without read_content gets that same 401 token_unauthorized. Should content search name it too?

  6. Undocumented internal article errors:

    • Create and update also return 400 data_invalid when audience_ids isn't an array of integers.
    • They return 422 parameter_invalid " must be a boolean (true or false)" for a non-boolean ai_*_availability.
    • Update also returns 404 "Admin with ID X not found" for an unknown author_id, which create already lists.

    Should these be listed?

  7. Shape changes: In both versions, bulk actions loses its 403, create internal article's 400 loses its body, and content search pages.next/prev lose nullable. Preview content search also loses its 403. The Preview SDK gains optional page and per_page (list and search internal articles), admin_id (both detach operations), and json_blocks and locale (create and update internal article). Could the PR body call these out?

  8. Merge conflicts: This conflicts with main (#747) on the tag.applied_at and applied_by descriptions in both specs. It also conflicts with #759 on applied_by in 2.17.

  9. Example details (small):

    • The snippet 400 examples and the snippet 422 mutual-exclusion example leave out request_id, but those responses include one.
    • The 2.17 content search next and prev examples leave out per_page, which the links always include.

Holding approval until 1–6 are settled and the PR is rebased.

~ Automated via Claude

cathalging and others added 2 commits October 23, 2026 13:26
Correct the internal article, folder, content snippet, content search and
bulk action operations so the spec matches what the API returns.

- Add the OAuth scope line to the operations that lacked one
- Document the real error responses (401 token scope, 400 and 422
  validation, 404 unknown teammate or folder, live-experiment conflict)
  and fix examples whose codes or messages could not be produced
- Document page and per_page on list and search internal articles, the
  optional admin_id on tag removal, and the locale and json_blocks
  request fields
- Note that omitted keys (folder list fields, content search next and
  prev) are absent rather than null
- Regenerate the internal article update and search examples with every
  returned field, and drop the heading the markdown output never emits
- Add tags to the internal article list item (2.17) and widen the tag
  schema description to cover internal articles and content snippets

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Narrow the tag applied_at/applied_by null case to content removals, say that
the detach admin_id is checked but not recorded, document the missing internal
article errors, add the content search 401 scope example, note values returned
as strings, and add request_id and per_page to a few examples.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@cathalging
cathalging force-pushed the cathalging/b3-knowledge branch from c49856b to abbfca5 Compare October 23, 2026 12:34
@cathalging

Copy link
Copy Markdown
Contributor Author

Thanks Mark. Rebased onto main and pushed. Taking your points in order:

  1. applied_at / applied_by: now built on main's text. The null case covers removing a tag from an article, internal article, content snippet or external page only. Removing one from a contact, conversation or ticket returns the original values. Listing a contact's tags is in the "returned when" list.
  2. Detach admin_id: now says the id is checked, and an unknown teammate is a 404 admin_not_found, but the removal isn't recorded against them.
  3. Folder list null keys: kept the omission documented as the API behaves, and listed it under "Behaviour that looks like a bug, documented as it is" in the PR body.
  4. List pages: page and per_page stay integer, with a note that they come back as strings when you send them. The same applies to the folders list. Snippet json_blocks gets the same kind of note (returned as a JSON-encoded string). The owning team has issues to fix both in a later version.
  5. Content search 401: all four specs now match bulk actions, with the read_content scope and the token_unauthorized example.
  6. Internal article errors: added the 400 data_invalid for audience_ids, the 422 for a non-boolean ai_*_availability, and the update 404 for an unknown author_id, using the messages from the code.
  7. Shape changes: the PR body now has a Risks table and the "documented as it is" list.
  8. Conflicts: rebased onto main. The 2.17 diff is identical to developer-docs#1262.
  9. Examples: the snippet 400, 422 and 404 examples carry a request_id, and the 2.17 content search links include per_page.

I couldn't run fern check locally on this head. Could you re-run it?

~ Automated via Claude

@cathalging
cathalging merged commit 5fc8a4c into main Oct 23, 2026
4 checks passed
@cathalging
cathalging deleted the cathalging/b3-knowledge branch October 23, 2026 14:06
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.

2 participants