Skip to content

docs(x402): document error_detail.payment_rejected on the 402 challenge body - #45

Open
yakimoto wants to merge 3 commits into
mainfrom
docs/x402-payment-rejected-field
Open

docs(x402): document error_detail.payment_rejected on the 402 challenge body#45
yakimoto wants to merge 3 commits into
mainfrom
docs/x402-payment-rejected-field

Conversation

@yakimoto

@yakimoto yakimoto commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Keeps the public 402 contract aligned with the gateway's deny-visibility fix: when a submitted payment is rejected, the gateway now publishes a sanitized deny verdict on the challenge body, and consumers generating types or docs from this spec could not discover it.

While confirming the wire shape against the gateway (review follow-up), the pre-existing error_detail modeling turned out to be wrong: the gateway nests the bare error object directly under error_detail ({ code, message, ... }), not the Error envelope ({ error: { ... } }). The inner object is now extracted as a reusable ErrorBody component (referenced by Error, so every other response's effective schema is unchanged) and the challenge composes it:

error_detail:
  allOf:
    - $ref: '#/components/schemas/ErrorBody'
    - type: object
      properties:
        payment_rejected:
          type: object
          required: [reason, rail]
          properties:
            reason: { type: string }
            rail: { type: string }

payment_rejected is optional and conditional: absent on an ordinary unpaid challenge, so existing consumers are unaffected. redocly lint passes (pre-existing warnings only).


Note

Low Risk
Documentation-only OpenAPI and changelog updates; optional additive field with no implementation changes in this repository.

Overview
Aligns the public OpenAPI contract with gateway behavior (wave-gateway#817/#818): when a client submits a payment and the gateway rejects it, the 402 x402 challenge can carry a sanitized deny verdict on X402PaymentRequired.error_detail.

error_detail is now modeled as allOf the existing Error ref plus an optional payment_rejected object with required reason (closed snake_case deny vocabulary) and rail (payment rail or unknown). The field is conditional—present only on reject-after-submit, not on a normal unpaid challenge—so existing consumers that ignore unknown properties stay unaffected.

CHANGELOG.md records the additive schema under Unreleased.

Reviewed by Cursor Bugbot for commit 740f956. Configure here.

Note

Document error_detail.payment_rejected on the X402 challenge body

  • Extracts a reusable ErrorBody schema in openapi.yaml so the Error envelope and X402Challenge.error_detail can reference the bare error object without the wrapping error member.
  • Adds an optional payment_rejected field (reason, rail) to X402Challenge.error_detail to surface sanitized denial details when a submitted payment is rejected.
  • Behavioral Change: error_detail now references ErrorBody directly rather than the Error envelope, meaning clients parsing error_detail should not expect a nested error key.

Macroscope summarized b51c42e.

…ge body

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
@cursor

cursor Bot commented Aug 7, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_72689e86-3d94-46c1-9491-fc1621a71f31)

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 1 minute

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 95cc5631-f4df-4363-a986-e2da8ac3b69c

📥 Commits

Reviewing files that changed from the base of the PR and between 6649096 and b51c42e.

📒 Files selected for processing (2)
  • CHANGELOG.md
  • openapi.yaml

Comment @coderabbitai help to get the list of available commands.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Document X402 402 challenge payment_rejected deny verdict in OpenAPI

📝 Documentation ✨ Enhancement 🕐 10-20 Minutes

Grey Divider

AI Description

• Add optional error_detail.payment_rejected object to the 402 (X402) challenge schema.
• Describe sanitized { reason, rail } deny verdict for rejected submitted payments.
• Record the additive contract change in the changelog for SDK/doc generation consumers.
Diagram

graph TD
  C[Client] --> G[Gateway] --> R{{"402 Challenge"}} --> S["openapi.yaml"] --> D["Docs/SDK generation"]
  S --> Ch["CHANGELOG.md"]

  subgraph Legend
    direction LR
    _usr[Client] ~~~ _svc[Service] ~~~ _resp{{Response}} ~~~ _file[File]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Extract payment_rejected into a reusable component schema
  • ➕ Improves readability of the inline allOf block
  • ➕ Enables reuse if other responses later expose the same verdict structure
  • ➕ Makes it easier to add enums/examples without bloating the response schema
  • ➖ Slightly more indirection for readers jumping around the spec
  • ➖ May be premature if this structure remains unique to X402 402s
2. Model error_detail variants with oneOf (unpaid vs rejected)
  • ➕ More explicit discriminator-style modeling for clients
  • ➕ Can express mutual exclusivity and required fields per variant
  • ➖ Often increases client-generator complexity and can produce awkward types
  • ➖ Not necessary if payment_rejected is truly optional/conditional

Recommendation: Current approach (additive allOf augmentation on error_detail) is a good compatibility-first change for existing generators. Consider extracting a component schema later if the verdict shape is reused or grows (examples/enums), but it’s not required for this PR.

Files changed (2) +29 / -1

Enhancement (1) +23 / -1
openapi.yamlAdd optional error_detail.payment_rejected object to X402 402 schema +23/-1

Add optional error_detail.payment_rejected object to X402 402 schema

• Extends 'error_detail' from a plain 'Error' reference to an 'allOf' that includes an additional 'payment_rejected' object. Documents semantics and adds required '{ reason, rail }' string properties for sanitized rejection diagnostics.

openapi.yaml

Documentation (1) +6 / -0
CHANGELOG.mdChangelog entry for X402 payment_rejected diagnostic field +6/-0

Changelog entry for X402 payment_rejected diagnostic field

• Adds a bullet under Added documenting the new 'X402PaymentRequired.error_detail.payment_rejected' field. Notes the field is optional/conditional and intended for generated docs/types discovery.

CHANGELOG.md

devin-ai-integration[bot]

This comment was marked as resolved.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Risk: low. Left a non-blocking comment — Cursor Bugbot and Cursor Security Agent both skipped (Bugbot reported a usage limit), so required automated review signals did not complete successfully and human review is needed. No reviewers were assigned because no eligible non-author reviewers could be identified from available ownership/history.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Aug 7, 2026
@macroscopeapp

macroscopeapp Bot commented Aug 7, 2026

Copy link
Copy Markdown

Approvability

Verdict: Approved b51c42e

Documentation-only changes to OpenAPI spec file documenting existing gateway behavior. Author owns both modified files. The open review comment requests verification that documented nesting matches actual wire shape - a documentation accuracy concern, not a runtime safety issue.

You can customize Macroscope's approvability policy. Learn more.

…uard

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
@qodo-code-review

qodo-code-review Bot commented Aug 7, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (1) 📜 Skill insights (0)

Grey Divider


Informational

1. openapi.yaml exceeds 500 lines 📘 Rule violation ⚙ Maintainability
Description
The PR modifies openapi.yaml, which is at least 3,500+ lines long, exceeding the 500-line limit
for touched source files. This increases maintenance burden and violates the file-length constraint.
Code

openapi.yaml[R3547-3550]

+          allOf:
+            - $ref: '#/components/schemas/Error'
+            - type: object
+              properties:
Evidence
PR Compliance ID 2497975 requires each touched source file to be <=500 non-comment lines. The
modified openapi.yaml contains content at line ~3547+, demonstrating it is well above 500 lines.

Rule 2497975: Limit source file length to 500 lines of code
openapi.yaml[3546-3572]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`openapi.yaml` is far above the 500-line maximum for touched files.

## Issue Context
This PR adds schema documentation under `components/schemas/X402PaymentRequired/error_detail`, but the OpenAPI spec file is already several thousand lines long.

## Fix Focus Areas
- openapi.yaml[3547-3569]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context used
✅ Compliance rules (platform): 1 rule
Review mode: ⚖️ Balanced: This changes a public OpenAPI contract and conditional error schema, so it carries genuine compatibility and documentation correctness risk despite the small, localized diff.

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

@qodo-code-review

Copy link
Copy Markdown

Qodo Fixer

No findings are within the configured fix scope. To change which findings are fixed, adjust the setting on your Qodo configuration page.

macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Aug 7, 2026
… envelope

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

Open in Devin Review

Comment thread openapi.yaml
Comment on lines 3554 to +3557
error_detail:
$ref: '#/components/schemas/Error'
allOf:
- $ref: '#/components/schemas/ErrorBody'
- type: object

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Refactor changes the generated type for error_detail (breaking for existing consumers)

The extraction of ErrorBody leaves every other response byte-identical (the Error envelope now just $refs the extracted object), but error_detail itself changes shape: it used to generate as { error: { code, message, ... } } and now generates as { code, message, ..., payment_rejected? }. Consumers that already generated types from the previous spec and read error_detail.error.code will break at their next regeneration. The CHANGELOG records this under "Fixed" (CHANGELOG.md:9-16), which is reasonable if the old spec was wrong on the wire, but the claim that the gateway nests the bare error object is unverifiable from this repo — worth confirming against an actual 402 body before release, since it is effectively a breaking change for SDK consumers.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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