Skip to content

fix(openapi): improve spec quality for SDK generation#2620

Draft
grdsdev wants to merge 6 commits into
masterfrom
claude/busy-jones-8f20c4
Draft

fix(openapi): improve spec quality for SDK generation#2620
grdsdev wants to merge 6 commits into
masterfrom
claude/busy-jones-8f20c4

Conversation

@grdsdev

@grdsdev grdsdev commented Jul 8, 2026

Copy link
Copy Markdown

Summary

Series of fixes to openapi.yaml aimed at improving SDK-generation quality. Verified every change against redocly lint and, where behavior was in question, against the actual Go handlers in internal/api.

  • operationIds: added to all 59 operations so generated SDKs get readable method names instead of path-derived ones.
  • Broken $ref: /user/identities/{identityId} 401/403 responses pointed schema: at a Response object instead of ErrorSchema — invalid OpenAPI, now fixed.
  • Discriminator: added to the /factors/{factorId}/challenge response oneOf so typed SDKs can pick the right variant.
  • code_challenge_method casing: unified on S256 everywhere (server parses case-insensitively; discovery doc already advertised S256).
  • OAuth client dedupe: POST /admin/oauth/clients and POST /oauth/clients/register had a byte-identical inline request body, copy-pasted; extracted into OAuthClientCreateRequest.
  • Lint errors: fixed invalid patternProperties/optional keywords (not valid in OpenAPI 3.0) and added missing security blocks to 4 operations that were inconsistent with their siblings.
  • Missing 2xx/4xx responses: added real ones after checking each handler (e.g. GET /user was missing 401; GET /user/identities/authorize was missing an entirely undocumented 200 response — turns out skip_http_redirect=true returns real JSON, mirroring /sso, but neither the param nor the response were documented). For operations that generally have no 2xx or 4xx (redirect-only success flows, or endpoints with no rejectable input), added a .redocly.lint-ignore.yaml with comments instead of fabricating fake responses.
  • Split flat request bodies into oneOfs: /token, /verify, and /factors each crammed every variant's fields into one all-optional blob. Split into named schemas matching the real Go param structs (PasswordGrantParams, RefreshTokenGrantParams, etc.). /factors also gets a real discriminator on factor_type since that field lives in the body; /token//verify don't, since their dispatch key is either a query param (grant_type) or a choice between two body fields (token vs token_hash) — neither fits OpenAPI's discriminator model.
  • Along the way: fixed /token's 401/403 responses which were swapped (401 pointed at ForbiddenResponse, 403 at UnauthorizedResponse), and documented link_identity on the id_token grant, both real but previously undocumented.

redocly lint openapi.yaml passes clean (0 errors, 0 warnings, 11 explicitly-documented ignores).

Not included

CustomOAuthProviderSchema's create/update request bodies look similar but were deliberately left alone — create requires several fields and has real defaults, update is PATCH-style (all optional, no defaults since "absent" means "don't touch"). Merging them would misrepresent one of the two endpoints.

Test plan

  • redocly lint openapi.yaml — 0 errors, 0 warnings
  • redocly bundle — all new oneOf/discriminator schemas resolve correctly
  • No behavioral/Go code changes in this PR — spec-only

grdsdev added 6 commits July 8, 2026 17:43
- Add operationId to every operation (59 total) so SDK generators
  produce readable method names instead of path-derived ones.
- Fix /user/identities/{identityId} 401/403 responses that
  incorrectly $ref'd a Response object from inside `schema:` instead
  of ErrorSchema.
- Add discriminator to the /factors/{factorId}/challenge response
  oneOf so typed SDKs can pick the right variant.
Legacy endpoints documented lowercase "s256" while /oauth/authorize
documented uppercase "S256" for the same parameter. Server parses
this case-insensitively (ParseCodeChallengeMethod, oauthserver
authorize.go) and the discovery document already advertises "S256",
so standardize all docs on that casing.
POST /admin/oauth/clients and POST /oauth/clients/register carried a
byte-identical inline request schema. Extract it into
OAuthClientCreateRequest and $ref it from both. The PUT update body
stays inline since its shape genuinely differs (no client_type,
nothing required).
- Replace invalid patternProperties (SAMLAttributeMappingSchema.keys,
  /settings external) with additionalProperties.
- Drop invalid `optional: true` keyword on /settings sms_provider
  (properties are optional by default unless in `required`).
- Add missing `security: [APIKeyAuth]` to POST /callback,
  POST /oauth/clients/register, POST /oauth/token, GET /oauth/authorize
  — all were oversights vs. sibling operations that already declare it;
  none of these bypass the gateway apikey requirement.
Verified each flagged operation against its Go handler before touching
anything -- only added responses that are real, and left redirect-only
flows alone rather than fabricating a fake 2xx.

Genuine gaps fixed:
- GET /verify, GET /authorize: add 400 (GetExternalProviderRedirectURL/
  verifyTokenHash return real validation errors); /authorize also gets
  404 for a missing invite_token.
- GET /user: add 401 (UserAuth-guarded, was undocumented).
- GET /user/identities/authorize: LinkIdentity actually returns a 200
  JSON {url} when skip_http_redirect=true (mirrors the existing /sso
  pattern) but that query param and response were never documented.
  Added both, plus 400/401.
- GET /admin/sso/providers: add 401/403 to match every sibling admin
  list endpoint.

For the remaining 11 warnings the operations genuinely have no 2xx or
no 4xx path (redirect-only success, or no rejectable input) -- added
.redocly.lint-ignore.yaml with comments explaining why each is
intentional instead of inventing fake responses.
…est schemas

Each of these endpoints crammed every grant-type/verification-type/
factor-type's fields into one flat, all-optional object, so generated
SDKs got a single loosely-typed request struct instead of a real
choice of shapes. Split each into named request schemas per real Go
handler struct/validation (PasswordGrantParams, RefreshTokenGrantParams,
IdTokenGrantParams, PKCEGrantParams, Web3GrantParams, VerifyParams,
EnrollFactorParams), wired as oneOf.

/token and /verify dispatch on grant_type/token_hash-vs-token which
live outside (query param) or across (token vs token_hash) the request
body, so no discriminator applies there. /factors dispatches on
factor_type inside the body, so that one gets a real discriminator.

Also:
- fixed /token's 401/403 responses, which pointed at the wrong
  response ref (401 was ForbiddenResponse, 403 was UnauthorizedResponse).
- documented link_identity on the id_token grant and issuer's real
  default on TOTP enrollment -- both real, previously undocumented
  fields found while reading the handlers.
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.

1 participant