Skip to content

docs(api-reference): type organization response envelopes in OpenAPI spec - #254

Merged
aaitor merged 1 commit into
mainfrom
docs/2455-org-response-envelope-schemas
Jul 28, 2026
Merged

docs(api-reference): type organization response envelopes in OpenAPI spec#254
aaitor merged 1 commit into
mainfrom
docs/2455-org-response-envelope-schemas

Conversation

@aaitor

@aaitor aaitor commented Jul 28, 2026

Copy link
Copy Markdown
Member

What

Regenerates the standalone Organizations OpenAPI spec
(docs/api-reference/organizations-openapi.json) so the groups, budgets,
payment-method and wallet endpoints describe their real
{ success, <item(s)> } response bodies with typed schemas — previously they
carried only a prose description: 'Envelope `{ success, budget }`' and no
response schema, so the API reference and SDK codegen saw an untyped body.

Mirrors the backend change in nevermined-io/nvm-monorepo#2532.

Changes

  • +20 component schemas: 13 response envelopes
    (GroupResponseEnvelopeDto, GroupBudgetListResponseDto,
    OrganizationWalletWithdrawResponseDto, OrganizationsSuccessResponseDto, …)
    plus the item DTOs and transitive types they $ref
    (GroupBudgetResponseDto, OrganizationWalletBalancesDto,
    OrganizationWalletTokenBalanceDto, PaginationMetaDto, …), which had been
    pruned from the spec because the endpoints returned untyped bodies.
  • 27 success responses now $ref their envelope schema.
  • Purely additive: every existing description, path and schema is preserved
    (verified a no-op re-serialization is byte-identical to the committed file);
    all $refs resolve; no schema-level examples (OpenAPI 3.0 clean).

Test plan

  • All 82 component schemas present; all $refs resolve
  • 65 paths unchanged; only the 27 typed endpoints gained response schemas
  • Mintlify renders the Organizations API group with typed responses

Copilot AI review requested due to automatic review settings July 28, 2026 14:15
@aaitor
aaitor requested a review from a team as a code owner July 28, 2026 14:15
@mintlify

mintlify Bot commented Jul 28, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
Nevermined 🟢 Ready View Preview Jul 28, 2026, 2:16 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

Copilot AI 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.

Pull request overview

Updates the Organizations standalone OpenAPI spec (docs/api-reference/organizations-openapi.json) to replace previously untyped “envelope” response descriptions with concrete JSON response schemas, enabling typed API reference rendering and downstream SDK/codegen typing.

Changes:

  • Adds new component schemas for response envelopes and related DTOs (e.g., group budgets, payment methods, wallets, pagination meta).
  • Updates multiple success responses to include application/json response bodies that $ref the new envelope schemas.
  • Adjusts some existing response schema references to point to the new envelope DTOs.
Comments suppressed due to low confidence (2)

docs/api-reference/organizations-openapi.json:7082

  • OrganizationWalletTokenBalanceDto.formatted is declared as type: object but the description/example indicate it’s a human-readable string balance. This should be string (nullable) to avoid incorrect client types.
          "formatted": {
            "type": "object",
            "description": "Human-readable balance (null if the read failed)",
            "example": "1.5",
            "nullable": true
          }

docs/api-reference/organizations-openapi.json:7190

  • OrganizationWalletResponseDto.label is typed as object, but both the example and the request DTOs (CreateOrganizationWalletDto/UpdateOrganizationWalletDto) define label as a string. This should be string (nullable) so response typing matches the rest of the spec.
          "label": {
            "type": "object",
            "example": "Treasury",
            "nullable": true
          },

Comment thread docs/api-reference/organizations-openapi.json

@r-marques r-marques left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤖 Automated PR review — 🔴 Blocked

Reviewed origin/main...HEAD (1 file, +770/−27) with the code-reviewer agent, plus a source-fidelity pass diffing every added schema against the backend implementation in nvm-monorepo and the mirrored PR nevermined-io/nvm-monorepo#2532. Every finding below was re-verified directly against the source and @redocly/cli lint before posting.

One blocker: a response is typed with a schema the API does not return. Everything else is small, and the underlying work is careful — 19 of the 20 added schemas are faithful transcriptions of the source DTOs.

🔴 Blockers (1)

  • POST /organizations/{orgId}/groups/{groupId}/members (201) is typed with a body the API never returnsdocs/api-reference/organizations-openapi.json:2897(code-reviewer + fidelity pass, confidence high)

    The response is $ref: GroupResponseEnvelopeDto, i.e. { success, group: GroupResponseDto }. The handler returns a membership, not a group:

    // nvm-monorepo apps/api/src/organizations/groups/groups.controller.ts (addMember)
    return {
      success: true,
      membership: { id: m.id, groupId: m.groupId, orgMemberId: m.orgMemberId, isActive: m.isActive },
    }

    There is no group key in that response. The previous prose ("Member added to group") declared no shape, so this schema is invented rather than transcribed — the one place in the PR where that happens. Published as-is, the API reference tells integrators to read a field that will always be undefined.

    Note this originates upstream: nvm-monorepo#2532 applies the same wrong @ApiResponse({ type: GroupResponseEnvelopeDto }) to addMember, so the fix belongs in both PRs. It needs a GroupMemberResponseDto ({ id, groupId, orgMemberId, isActive }) plus an envelope keyed membership.

🟡 Should fix (3)

  • GET /organizations/{orgId}/groups/{groupId} — the description promises members, the new schema drops it:2730 (description) / :2734 ($ref) — (confidence high)

    The description says group is "a GroupResponseDto plus members: GroupMemberResponseDto[]", but GroupResponseEnvelopeDto refs a plain GroupResponseDto, which has no members. The backend sides with the description:

    // groups.service.ts
    async getGroup(orgId, groupId): Promise<GroupResponseDto & { members: GroupMemberResponseDto[] }>

    A rendered schema outranks prose for readers, so this endpoint is now under-documented — it was previously ambiguous, and is now confidently wrong about an omission. Needs its own envelope (allOf, or a GroupWithMembersDto). Related: GroupMemberResponseDto is named in the prose but defined nowhere in the spec.

  • Three new properties declare type: object but carry string examples(confidence high)

    Field Line Declared Real type
    OrganizationWalletTokenBalanceDto.atomic :7071 type: object, example "1500000" string | null
    OrganizationWalletTokenBalanceDto.formatted :7077 type: object, example "1.5" string | null
    OrganizationWalletResponseDto.label :7186 type: object, example "Treasury" string | null

    Mintlify will render these as object. Confirmed by lint delta: @redocly/cli lint emits 8 no-invalid-schema-examples on base and 11 on head — the delta is exactly these three.

    Root cause is the SWC design:type = Object footgun that nvm-monorepo's own CLAUDE.md documents: those @ApiProperty decorators omit type: String. Corroboration from inside this same PR — GroupPaymentMethodResponseDto.brand/last4/alias/orgId come out correctly as type: string precisely because their source decorators do pass type: String. Fix to type: string + nullable: true here, and in the source decorators, or the next re-export reintroduces it.

  • Merge ordering: nvm-monorepo#2532 is still OPEN, not merged(confidence high)

    The docs describe the shape #2532 annotates. That PR's own DTO file states "Swagger metadata only — the wire shape is unchanged", so the { success, … } envelope is already today's runtime behaviour and merging the docs first does not misdescribe the deployed API. Flagging it only because the blocker above must be fixed on both sides — if the docs fix lands alone, #2532 will re-introduce the wrong shape at the source.

💡 Good to have (3)

  • Coverage is now ~65% and the gap is domain-shaped. After this PR, 57 of 84 2xx responses carry a schema; 27 do not, of which 2 are legitimate 204s → 25 real bodies still prose-only: billing (7), webhooks (8), org members (5), customers (2), invitations (2), GET /organizations/user-info/{userId} (1). Groups/budgets/wallets now render full schemas while billing/webhooks render bare prose, which reads as inconsistency rather than staged work. Several of the untyped descriptions already spell the shape out inline ({ canceled, cancelAt, currentPeriodEnd }, { items, total }), so they are the cheapest follow-up.
  • All 4xx responses remain untyped, leaving OrganizationsErrorResponseDto (:6641) an orphan schema — Redocly flags it under no-unused-components. Pre-existing, but this was the natural moment to wire it up. (CreateOrganizationDto is the other pre-existing orphan.)
  • Durability of the hand-edit. This file is a manual paste of the API's own buildOrganizationsOpenApiDocument() output, so a future re-paste would silently drop all 20 hand-written schemas. #2532 landing is what makes that safe — worth a note in the repo's CLAUDE.md that organizations-openapi.json is hand-maintained, unlike its generated neighbours.

✅ Strengths

  • The two changed $refs are a genuine bug fix, not churn. POST …/groups 201 (:2616) and PATCH …/groups/{groupId} 200 (:2791) previously pointed straight at GroupResponseDto while the API returns the { success, group } envelope — this corrects documentation that was actively misleading.
  • Structurally clean. Valid OpenAPI 3.0.0; all 80 $refs resolve; no duplicate keys; nullable: true is correct 3.0 syntax; no schema-level examples. Adds 3 lint warnings and 0 errors.
  • Field-level fidelity is high — this is transcription, not guesswork, which is exactly why the one invented schema stands out. Verified against source: GroupBudgetResponseDto matches all 10 fields including the interval enum ["month","year","one_shot"] and status ["Active","Exhausted"]; GroupPaymentMethodResponseDto gets the nullable set exactly right; OrganizationWalletResponseDto correctly omits label from required because the source uses @ApiPropertyOptional; OrganizationWalletWithdrawResponseDto.txHash is nullable, matching Promise<{ userOpHash: string; txHash: string | null }>.
  • Good factoring. OrganizationsSuccessResponseDto is reused across 9 bare-{success} endpoints, PaginationMetaDto is extracted rather than inlined, and the …ResponseEnvelopeDto (singular) / …ListResponseDto (collection) convention is applied consistently across all 20 additions.
  • Editing this file by hand is correct. Verified, not assumed: the docs repo's only workflow is path-filtered to skills/nevermined-payments/**, and nvm-monorepo's two docs-sync workflows write docs/development-guide/* only (sync-api-changelog-docs.yml explicitly notes its target is "deliberately NOT under docs/api-reference/*"). No bot has ever touched this file. The CLAUDE.md "don't modify api-reference" rule does not bite here.

Comment-only review — no approval or change-request recorded, and nothing in this PR was modified.

@r-marques

Copy link
Copy Markdown
Member

Addendum — independent second pass

A second verification pass (mechanical diff of all 20 added schemas against nvm-monorepo origin/main @ 76cdf9665 and #2532's head @ 3f31d0e34) reproduced every finding above independently. Three additions, one of which refines a claim I made:

1. The fix for the blocker is cheaper than it looked — the DTO already exists. GroupMemberResponseDto is defined in the backend at apps/api/src/organizations/groups/dto/group.dto.ts:126-138 ({ id, groupId, orgMemberId, isActive }). It was simply never carried into this spec. So B1 needs a GroupMemberResponseEnvelopeDto keyed membership wrapping the existing DTO — no new modelling. The same DTO also resolves the dangling prose reference in S1.

2. Nothing in CI would have caught the blocker. No test in either repo pins the POST …/members response body — no spec asserts membership. B1 rests on the handler's literal return (groups.controller.ts:211-219), which is unambiguous, but the shape is currently unguarded on both sides. Worth a response-shape assertion alongside the fix, given #2532 already pins exactly this kind of thing for its own txHash — its new response-envelopes.spec.ts asserts withdraw.properties.txHash.type === 'string' and .nullable === true, explicitly to guard the SWC gotcha.

3. Correction to my "the CLAUDE.md rule does not bite here". That was right about the mechanical risk — verified, no workflow in either repo writes this file, so the hand-edit will not be clobbered. But it understated a real documentation conflict: this repo's CLAUDE.md:392 and :590 state a blanket "All the documentation included in the docs/api-reference folder is sourced from different repositories. Don't modify it directly", while CLAUDE.md:25 narrows the carve-out to the "payments / payments-py / cli / openclaw" subtrees. organizations-openapi.json falls in the gap between the two, so this PR is technically against the repo's stated rule even though nothing enforces it. Worth amending CLAUDE.md to record that this file is hand-maintained — otherwise the next contributor reasonably refuses to touch it.

Relatedly, sync-api-changelog-docs.yml:9 in nvm-monorepo asserts the api-reference tree "is mirrored by other workflows". No such workflow exists in either repo — that comment appears stale (caveat: only these two repos were searched).

Unchanged: the blocker stands, and it needs fixing in both this PR and #2532.

@aaitor
aaitor force-pushed the docs/2455-org-response-envelope-schemas branch from 9dc4756 to d6843c1 Compare July 28, 2026 14:54
@aaitor

aaitor commented Jul 28, 2026

Copy link
Copy Markdown
Member Author

Thanks — all points addressed and pushed (spec regenerated from the corrected DTOs; mirrors nvm-monorepo#2532):

🔴 r-marques blocker — member-add typed with a body the API never returns
Fixed. POST …/groups/{groupId}/members now $refs a new GroupMemberEnvelopeDto ({ success, membership: GroupMemberResponseDto }), matching the handler's { success, membership: { id, groupId, orgMemberId, isActive } }. GroupMemberResponseDto is added to the spec.

Copilot — type: object on nullable string fields
Fixed the same SWC decorator-metadata gotcha (a string | null field with no explicit @ApiProperty({ type }) emits design:type = Object) across every affected field in the surfaced schemas: OrganizationWalletResponseDto.label, OrganizationWalletTokenBalanceDto.atomic + .formatted, and GroupResponseDto.description — all now type: string, nullable: true. A backend smoke test pins each one.

Out of scope (pre-existing, untouched by this response-typing change): the same nullable-object pattern in unrelated request/other DTOs (CreateOrganizationDto, CustomerResponseDto, InvitationResponseDto, activity/tier DTOs, and the budget maxTransactions request fields). Happy to sweep those in a follow-up.

…spec

Regenerates the standalone Organizations OpenAPI spec so the groups,
budgets, payment-method and wallet endpoints carry typed `{ success, … }`
response schemas instead of prose-only descriptions.

Adds 24 component schemas (16 response envelopes + the item DTOs and
transitive types their responses reference, which were previously pruned
because the endpoints returned untyped bodies) and wires all 27 success
responses to their envelope via `$ref`.

- Member-add is a membership envelope (`{ success, membership }`).
- GET group returns a `GroupWithMembersDto` (group + typed `members[]`).
- Nullable string fields (wallet label, token atomic/formatted balance,
  group description) render as `string` not `object`.

Mirrors nvm-monorepo #2532.
@aaitor
aaitor force-pushed the docs/2455-org-response-envelope-schemas branch from d6843c1 to ff7d417 Compare July 28, 2026 14:59
@aaitor
aaitor merged commit bcff136 into main Jul 28, 2026
3 checks passed
@aaitor
aaitor deleted the docs/2455-org-response-envelope-schemas branch July 28, 2026 15:25
aaitor added a commit that referenced this pull request Jul 29, 2026
…#255)

Follow-up to #254. Five fields in the Organizations OpenAPI spec rendered
as `type: object` instead of their scalar type — the SWC decorator-metadata
gotcha on `T | null` DTO fields, fixed in the source in nvm-monorepo #2532:

- TierCatalogRowDto.stripeLookupKey -> string, nullable
- InvitationResponseDto.name + PublicInvitationInfoDto.name -> string, nullable
- Create/UpdateGroupBudgetDto.maxTransactions -> number, nullable

Codegen/SDK consumers now get the correct scalar types.
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