feat(openapi): shared error responses + 429/500 coverage + externalDocs#9
Open
feat(openapi): shared error responses + 429/500 coverage + externalDocs#9
Conversation
…e-limit/server-error coverage Tighten the public OpenAPI 3.1 snapshot so every operation documents the complete error surface we already enforce at runtime, and give coding-agent tools and SDK generators everything they need to emit good retry logic. Structural additions - components.responses - BadRequest — 400, payload validation failures - Unauthorized — 401, missing/invalid/revoked X-API-Key - TooManyRequests — 429, rate limit exceeded; retry with jitter - InternalServerError — 500, unexpected failure; contact support Each response reuses the pre-existing components.schemas.Error shape, so there's now a single source of truth for every error body we return. - externalDocs at the spec root pointing at https://leadmagic.io/docs so docs renderers surface a "full documentation" link. - info.termsOfService = https://leadmagic.io/terms. Per-operation changes (applied uniformly across all 19 operations) - Replaced every inline 400 (17 ops) and 401 (19 ops) response with $ref to the shared components.responses so the copy, status description, and schema never drift again. - Added 429 and 500 $refs to every operation. Previously only /profile-search declared 429 and no operation declared 500 even though the runtime returns both. Downstream SDK generators (Speakeasy, OpenAPI Generator, Fern) will now emit retry and backoff handlers for rate limits and transient server errors. Info-level tidy - Rewrote info.description: platform-neutral, points to the README for credit costs / MCP sign-in, drops the old "public docs not fully uniform" caveat that had no place in rendered docs. - Reordered info.* into the canonical OpenAPI key sequence (title, summary, description, version, termsOfService, contact, license) for consistency with Redocly/Scalar output. Consistency guarantees - Regenerated leadmagic-openapi-3.1.json from the YAML source of truth. This incidentally fixed a pre-existing YAML/JSON drift: company_facebook_url / company_twitter_url were present in the /email-finder 200 response's YAML but missing from the JSON. The two files are now structurally identical. - All 19 operationIds, summaries, descriptions, tags, and property-level examples preserved byte-for-byte. - Response maps within each operation reordered by status code (2xx, 4xx, 5xx) so the generated docs read top-to-bottom. Verification - swagger-cli validate: both YAML and JSON pass. - spectral lint: zero errors. - tsc --noEmit: clean. - CI deprecated-example regex: no singular example: / "example": fields found. - README required sections still present. - No hardcoded credentials. Made-with: Cursor
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.
Summary
Comprehensive audit and upgrade of
leadmagic-openapi-3.1.yaml/.jsonto bring the public spec up to the level that SDK generators, docs renderers (Redocly, Scalar, Swagger UI), and MCP/LLM tooling need in order to emit correct retry logic, full error handling, and complete API context.No operation signatures, schemas, tag taxonomy, or property-level examples were changed — every request/response shape, operationId, and example value is preserved byte-for-byte. All new content is additive or consolidation.
What changed
New shared error responses (
components.responses)BadRequestUnauthorizedX-API-Keymissing, invalid, or revoked.TooManyRequestsInternalServerErrorsupport@leadmagic.iowith the correlation id.All four
$refthe existingcomponents.schemas.Errorshape, so there's a single source of truth for every error body the API returns.Per-operation response-map updates (all 19 operations)
400responses (each a 4-line block duplicating the same schema ref + description)$ref: "#/components/responses/BadRequest"401responses$ref: "#/components/responses/Unauthorized"429on/profile-search; 0 × elsewhere$ref: "#/components/responses/TooManyRequests"on every operation500declared anywhere$ref: "#/components/responses/InternalServerError"on every operationDownstream impact: SDK generators (Speakeasy, OpenAPI Generator, Fern, etc.) will now emit retry-on-
429and backoff-on-5xxhandlers out of the box. Previously they had no signal that these responses exist, so callers had to write retry logic by hand.Spec-root additions
externalDocspointing at leadmagic.io/docs. Docs renderers use this to surface a "full documentation" link above the tag navigation.info.termsOfService: https://leadmagic.io/terms.info.descriptionto be platform-neutral, point to the README for credit costs / MCP sign-in, and drop the "public docs not fully uniform on field naming" caveat that was showing up in rendered docs.info.*into the canonical OpenAPI key sequence (title,summary,description,version,termsOfService,contact,license) for consistency with Redocly/Scalar output.Consistency fix
Regenerated
leadmagic-openapi-3.1.jsonfrom the YAML (the source that everyone edits). This incidentally fixed a pre-existing YAML/JSON drift:company_facebook_urlandcompany_twitter_urlwere present in the/email-finder200 response's YAML definition but missing from the JSON file. After this PR, the two files are structurally identical (diffable round-trip viajs-yaml).Why this matters
The spec was syntactically valid and lint-clean before, but three gaps were blocking high-quality SDK generation and AI/MCP consumers:
SECURITY.mddocumented rate limits, but the OpenAPI contract did not. Any generator reading only the spec had no way to know a429could come back.5xxsurface declared. Same issue — even though the runtime returns500on unexpected failures, the spec implied only2xx/4xxwere possible.400block and 19 copies of the same401block meant any future copy tweak would require a 36-place find-and-replace, which guarantees drift over time. Centralizing on$refremoves that class of bug.Diff shape
The unified diff is large (~4k insertions / ~3.5k deletions) because the YAML emitter re-serializes the whole file with its own quoting/indentation style. The semantic diff is small and surgical:
components.responsesadded.$refs (17 × 400, 19 × 401, 19 × 429, 19 × 500 — the 429/500 additions replace zero-or-one prior inline declarations).externalDocsblock added.info.termsOfServicefield added.info.descriptionrewritten.Reviewers can verify the semantic delta with:
Verification (all run locally against the final files)
npx @apidevtools/swagger-cli validate leadmagic-openapi-3.1.yaml— valid.npx @apidevtools/swagger-cli validate leadmagic-openapi-3.1.json— valid.npm run lint:openapi(Spectral) — no results with severityerror.npm run typecheck(tsc --noEmit) — clean.grep -nE '(^|[^s])"example"[[:space:]]*:|(^|[^s])example[[:space:]]*:') — zero matches (all examples use OpenAPI 3.1 pluralexamples).test-api.tsor the specs.Follow-ups (intentionally out of scope)
examplesblocks for the 17 POST endpoints. Property-level examples are comprehensive already; adding duplicate operation-level fixtures would add ~800 lines of near-duplicate content. Will file a separate PR if/when we have a clean way to generate them from the live API.swagger-cli→@redocly/cli(the CLI emits a deprecation notice). Low priority; validation still works.