Skip to content

docs(api): align idempotency contract#4841

Merged
koala73 merged 1 commit into
mainfrom
codex/docs-idempotency-contract-4828
Jul 5, 2026
Merged

docs(api): align idempotency contract#4841
koala73 merged 1 commit into
mainfrom
codex/docs-idempotency-contract-4828

Conversation

@koala73

@koala73 koala73 commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Summary

WorldMonitor's API docs now describe Idempotency-Key the way the shipped OpenAPI contract and standalone handlers actually behave. run-scenario is documented as keyed-idempotent for retries, 409/422 guidance now matches the in-flight and payload-mismatch semantics, and the five standalone write endpoints from PR #4792 are called out in their API docs.

The docs also spell out the client-retry limits that matter: retryable 5xx responses are not cached for replay, checkout replays are retained for 10 minutes, and idempotency keys are scoped to the authenticated caller or source IP.

Closes #4828.

Verification

  • /opt/homebrew/bin/gh-axi issue view 4828 --full
  • /opt/homebrew/bin/gh-axi search prs "4828 OR idempotency OR Idempotency-Key" -> count: 0
  • node --test tests/docs-idempotency-contract.test.mjs
  • node --test tests/openapi-idempotency-contract.test.mjs
  • npm run lint:api-contract
  • npm run docs:check
  • ./node_modules/.bin/markdownlint-cli2 docs/usage-errors.mdx docs/api-commerce.mdx docs/api-notifications.mdx docs/api-platform.mdx
  • git push -u origin codex/docs-idempotency-contract-4828 pre-push hook: typecheck, API typecheck, boundary/safe HTML/rate-limit/premium-fetch guards, edge bundle/tests, markdown/MDX lint, and version sync all passed

Compound Engineering
GPT_5

@koala73
koala73 requested a review from SebastienMelki as a code owner July 5, 2026 11:06
@vercel

vercel Bot commented Jul 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
worldmonitor Ready Ready Preview, Comment Jul 5, 2026 11:09am

Request Review

@greptile-apps

greptile-apps Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR aligns the WorldMonitor API documentation with the idempotency contract already shipped in the handlers: 409/422 semantics are now precisely described, the five standalone write endpoints (/api/create-checkout, /api/customer-portal, /api/notify, /api/user-prefs, /api/notification-channels) are called out with their Idempotency-Key replay windows, and POST /api/notify is correctly re-described as an authenticated PRO endpoint rather than an internal relay ingestion point.

  • A new Idempotency-Key reference section is added to docs/usage-errors.mdx covering scoping rules, replay contract, in-flight lock behaviour, and the distinction between retryable and non-retryable failure modes.
  • api/api-route-exceptions.json reclassifies api/notify.ts from ops-admin to internal-helper with an updated reason reflecting Clerk bearer + PRO auth, matching the pre-existing handler implementation.
  • tests/docs-idempotency-contract.test.mjs is introduced to machine-assert that all key prose claims in the docs are backed by actual handler wiring and the generated OpenAPI spec.

Confidence Score: 4/5

Safe to merge after the minor doc inconsistencies are addressed; no handler or runtime logic is changed.

The changes are purely documentation and registry metadata. Three small inconsistencies were found: the status-code table now has 422 above 413 (out of numeric order), the customer-portal idempotency bullet is missing the 'with an identical body' qualifier present on every other endpoint, and the brief-why-matters.ts registry entry contains a stale cross-reference to notify.ts as 'cron-only'. None of these affect runtime behaviour, but the missing qualifier could mislead API clients about when a 422 is returned.

docs/usage-errors.mdx (table ordering) and api/api-route-exceptions.json (stale cross-reference in the unchanged brief-why-matters.ts entry) warrant a quick look before merge.

Important Files Changed

Filename Overview
docs/usage-errors.mdx Adds the Idempotency-Key section, updates 409/422 rows, and rewrites the write-retry guidance. The 422 row is inserted above the existing 413 row, breaking numeric ordering in the status table.
api/api-route-exceptions.json Reclassifies api/notify.ts from ops-admin to internal-helper with an updated reason reflecting Clerk bearer + PRO auth; the unchanged brief-why-matters.ts entry now contains a stale cross-reference to notify.ts as 'cron-only'.
docs/api-commerce.mdx Adds Idempotency-Key bullets for POST /api/create-checkout (10-minute window) and POST /api/customer-portal; customer-portal bullet omits 'with an identical body' qualifier present on all other endpoints.
docs/api-notifications.mdx Adds Idempotency-Key bullet to POST /api/notification-channels and rewrites POST /api/notify from internal relay endpoint to authenticated PRO event-publish endpoint; language is consistent with the handler contract.
docs/api-platform.mdx Adds Idempotency-Key bullet to POST /api/user-prefs section; change is minimal and consistent with other endpoint patterns.
tests/docs-idempotency-contract.test.mjs New test file that validates all idempotency doc claims against handler source code and the OpenAPI spec; covers key contract phrases, retry guidance, standalone endpoint listing, handler wiring, and notify auth model.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Client
    participant API as POST /api/:endpoint
    participant IdempotencyStore as Idempotency Store (Redis, 24h)

    Note over Client,IdempotencyStore: First request (no prior key record)
    Client->>API: POST + Idempotency-Key: K + body B
    API->>IdempotencyStore: Acquire in-flight lock for (caller, K)
    IdempotencyStore-->>API: Lock acquired
    API->>API: Execute operation
    API->>IdempotencyStore: Store response (status + body + Content-Type)
    API-->>Client: 2xx + Idempotent-Replayed: false

    Note over Client,IdempotencyStore: Retry with same key + identical body
    Client->>API: POST + Idempotency-Key: K + body B
    API->>IdempotencyStore: Look up (caller, K)
    IdempotencyStore-->>API: Completed response found
    API-->>Client: Replay (same status/body) + Idempotent-Replayed: true

    Note over Client,IdempotencyStore: Retry while first request is still running
    Client->>API: POST + Idempotency-Key: K + body B
    API->>IdempotencyStore: Look up (caller, K)
    IdempotencyStore-->>API: In-flight lock held
    API-->>Client: 409 idempotency_conflict + Retry-After: 2

    Note over Client,IdempotencyStore: Retry with same key but different body
    Client->>API: POST + Idempotency-Key: K + body B'
    API->>IdempotencyStore: Look up (caller, K)
    IdempotencyStore-->>API: Body hash mismatch
    API-->>Client: 422 idempotency_key_reused

    Note over Client,IdempotencyStore: Original execution returns 5xx
    Client->>API: POST + Idempotency-Key: K + body B
    API->>API: Execute operation → 5xx
    API->>IdempotencyStore: Release lock (response NOT cached)
    API-->>Client: 5xx (retryable, backoff required)
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Client
    participant API as POST /api/:endpoint
    participant IdempotencyStore as Idempotency Store (Redis, 24h)

    Note over Client,IdempotencyStore: First request (no prior key record)
    Client->>API: POST + Idempotency-Key: K + body B
    API->>IdempotencyStore: Acquire in-flight lock for (caller, K)
    IdempotencyStore-->>API: Lock acquired
    API->>API: Execute operation
    API->>IdempotencyStore: Store response (status + body + Content-Type)
    API-->>Client: 2xx + Idempotent-Replayed: false

    Note over Client,IdempotencyStore: Retry with same key + identical body
    Client->>API: POST + Idempotency-Key: K + body B
    API->>IdempotencyStore: Look up (caller, K)
    IdempotencyStore-->>API: Completed response found
    API-->>Client: Replay (same status/body) + Idempotent-Replayed: true

    Note over Client,IdempotencyStore: Retry while first request is still running
    Client->>API: POST + Idempotency-Key: K + body B
    API->>IdempotencyStore: Look up (caller, K)
    IdempotencyStore-->>API: In-flight lock held
    API-->>Client: 409 idempotency_conflict + Retry-After: 2

    Note over Client,IdempotencyStore: Retry with same key but different body
    Client->>API: POST + Idempotency-Key: K + body B'
    API->>IdempotencyStore: Look up (caller, K)
    IdempotencyStore-->>API: Body hash mismatch
    API-->>Client: 422 idempotency_key_reused

    Note over Client,IdempotencyStore: Original execution returns 5xx
    Client->>API: POST + Idempotency-Key: K + body B
    API->>API: Execute operation → 5xx
    API->>IdempotencyStore: Release lock (response NOT cached)
    API-->>Client: 5xx (retryable, backoff required)
Loading

Comments Outside Diff (1)

  1. api/api-route-exceptions.json, line 446 (link)

    P2 Stale cross-reference to notify.ts as cron-only

    This entry's reason still says "Same shape constraint as api/notify.ts (also cron-only)", but this PR reclassifies notify.ts from an internal, cron/relay-secret endpoint to an authenticated PRO user-facing endpoint. The parenthetical (also cron-only) now contradicts the updated classification and will mislead the next developer auditing the registry to understand notify.ts's auth model.

Reviews (1): Last reviewed commit: "docs(api): align idempotency contract" | Re-trigger Greptile

Comment thread docs/usage-errors.mdx
Comment on lines +38 to 40
| `409` | `Idempotency-Key` request still in progress, or another conflict such as duplicate webhook registration. | Yes for keyed in-flight requests - honor `Retry-After: 2`; otherwise no |
| `422` | `Idempotency-Key` reused with a different request body. | No - resend the original body or use a new key |
| `413` | Payload too large | No |

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.

P2 Status code table is out of numeric order

The 422 row was inserted before the existing 413 row, breaking the ascending numeric ordering that the rest of the table follows (405 → 409 → 422413 → 429). Readers scanning for a specific code by number will find the table inconsistent.

Suggested change
| `409` | `Idempotency-Key` request still in progress, or another conflict such as duplicate webhook registration. | Yes for keyed in-flight requests - honor `Retry-After: 2`; otherwise no |
| `422` | `Idempotency-Key` reused with a different request body. | No - resend the original body or use a new key |
| `413` | Payload too large | No |
| `409` | `Idempotency-Key` request still in progress, or another conflict such as duplicate webhook registration. | Yes for keyed in-flight requests - honor `Retry-After: 2`; otherwise no |
| `413` | Payload too large | No |
| `422` | `Idempotency-Key` reused with a different request body. | No - resend the original body or use a new key |

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread docs/api-commerce.mdx

- **Auth**: Clerk bearer + active entitlement
- **Response**: `{ "portalUrl": "..." }`
- **Idempotency**: optional `Idempotency-Key` supported. Retrying the same key replays the original portal response instead of creating another portal attempt.

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.

P2 Missing "with an identical body" qualifier for POST /api/customer-portal

Every other endpoint's idempotency bullet explicitly states "with an identical body" to communicate that body-matching is required for replay. The customer-portal bullet omits this, which could mislead clients into thinking they can replay with a different body and still get the cached response (they'd actually receive 422 idempotency_key_reused).

Suggested change
- **Idempotency**: optional `Idempotency-Key` supported. Retrying the same key replays the original portal response instead of creating another portal attempt.
- **Idempotency**: optional `Idempotency-Key` supported. Retrying the same key with an identical body replays the original portal response instead of creating another portal attempt.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@mintlify

mintlify Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

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

Project Status Preview Updated (UTC)
WorldMonitor 🟢 Ready View Preview Jul 5, 2026, 11:17 AM

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

@koala73
koala73 merged commit 131cfcc into main Jul 5, 2026
26 checks passed
@koala73
koala73 deleted the codex/docs-idempotency-contract-4828 branch July 5, 2026 11:47
koala73 added a commit that referenced this pull request Jul 5, 2026
…son — back under the ~1MB scanner cap (#4852) (#4853)

Today's per-op doc injections (#4843 429 blocks, #4841 idempotency, #4844
examples) grew the minified public/openapi.json 752KB -> 1.04MB, crossing
the ~1MB body cap orank's function-calling validator imposes: the Access
check function-calling-compat flipped PASS -> 'API spec found but couldn't
validate function calling compatibility' (score 91 -> 90). elevenlabs
(1.8MB) and openrouter (1.5MB) hit the identical error path; sub-800KB
specs get computed verdicts.

Hoist the byte-identical non-2xx response objects (429 x193, default x193,
401 x186, 400, 403, 409, 422) into components.responses $refs when
emitting the JSON artifact only - semantically identical OpenAPI 3.1,
validated with @seriousme/openapi-schema-validator. 2xx responses stay
inline (orank credits only the inline responses['200'] schema). YAML
sources keep their inline copies for Mintlify and the contract tests.

Result: 812,276 bytes (10 shared components, 975 refs), deterministic
rebuild, lossless roundtrip proven by test. A 950KB budget guard test
stops the next injector from silently re-crossing the cap.

Claude-Session: https://claude.ai/code/session_013AWjvnpnJTE6G2PaYk9iT1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant