docs(chat): reconcile the resume-stream contract with what the route returns - #287
docs(chat): reconcile the resume-stream contract with what the route returns#287sweetmantech wants to merge 1 commit into
Conversation
…returns Three drifts found by live testing of recoupable/api#809, all published today. 1. ChatStreamErrorResponse declared `{ status, message }` with `message` required. Every 4xx on this route actually returns `error`, and validation failures add `missing_fields`. Observed on preview: {"status":"error","missing_fields":["startIndex"], "error":"startIndex must be a non-negative integer"} {"status":"error","error":"Forbidden"} The schema is pre-existing and backs 401/403/404 as well as the 400, so every documented error body on this endpoint was wrong. 2. The `account_id` query override shipped in api#809 and was undocumented. Documented with the authorisation rule, since the interesting part is that it is validated rather than trusted — a personal key passing someone else's id gets 403. 3. `x-workflow-stream-tail-index` ships on the 200 and was undocumented. The tail-index description states the semantic that cost a round of rework downstream: headers are sent before the body, so the value is the tail when the read OPENED, not where the response ended. A read advertising a tail of 9 went on to deliver 22 chunks. Documented as a base for computing absolute positions, not a resume point. Verified: file parses; params resolve to [chatId(path), account_id(query), startIndex(query)]; 200 headers to both; error schema requires [status, error]. Applied via anchored edits — research.json does not round-trip through json.dumps byte-for-byte. Refs recoupable/chat#1923 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
📝 WalkthroughWalkthroughThe OpenAPI documentation adds an optional ChangesChat stream API contract
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related issues
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@api-reference/openapi/research.json`:
- Around line 820-824: Add a minimum constraint of 0 to the schema for the
x-workflow-stream-tail-index header, preserving its integer type and existing
description so negative zero-based indexes are rejected.
- Around line 789-798: Update the OpenAPI operation containing the account_id
query parameter and its 400/403 response descriptions: document malformed
account_id values in the 400 response alongside invalid startIndex, and revise
the 403 stream authorization/account-mismatch text to describe bearer-token
limitations and denied account overrides rather than ownership failure.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 00ce2337-4aa5-4c88-895f-ae5aa859d69f
📒 Files selected for processing (1)
api-reference/openapi/research.json
| { | ||
| "name": "account_id", | ||
| "in": "query", | ||
| "required": false, | ||
| "description": "Act on a chat belonging to another account. Only honoured for a key that is authorised for that account — an organization key may pass a member account's id; a personal key passing anyone else's is rejected with `403 Access denied to specified account_id`. Omit it to act as the authenticated account.", | ||
| "schema": { | ||
| "type": "string", | ||
| "format": "uuid" | ||
| } | ||
| }, |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the referenced OpenAPI segment and nearby responses/descriptions.
sed -n '730,890p' api-reference/openapi/research.json
echo '--- search account_id occurrences ---'
rg -n '"account_id"|account_id|Access denied to specified account_id|Invalid startIndex|startIndex' api-reference/openapi/research.jsonRepository: recoupable/docs
Length of output: 9336
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
import json
from pathlib import Path
path = Path("api-reference/openapi/research.json")
data = json.loads(path.read_text())
paths = data["paths"]
path = "/api/chat/{chatId}/stream"
op = paths[path]["get"]
params = {p["name"]: p for p in op.get("parameters", [])}
account_id_param = params.get("account_id", {})
startIndex_param = params.get("startIndex", {})
responses = op.get("responses", {})
print("operation_security_types:", [list(s.keys())[0] for s in op.get("security", [])])
print("has_account_id_param:", bool(account_id_param))
print("account_id_param_required:", account_id_param.get("required"))
print("account_id_param_description:", account_id_param.get("description").replace("\n", " ")[:240] if account_id_param.get("description") else "")
print("account_id_param_format:", account_id_param.get("schema", {}).get("format") if account_id_param.get("schema") else "")
print("startIndex_param_min:", startIndex_param.get("schema", {}).get("minimum") if startIndex_param.get("schema") else "")
print("operation_description_contains_account_id:", "account_id" in (op.get("description") or ""))
print("operation_description_contains_own:", bool(("own" in (op.get("description") or "").lower()) or ("ownership" in (op.get("description") or "").lower())))
for status in ["400", "401", "402", "403"]:
resp = responses.get(status, {})
print(status, "contains_account_id=", bool(resp.get("description") and "account_id" in resp.get("description", "")), "contains_account_or_others=", any(token in (resp.get("description") or "").lower() for token in ["authenticated account", "different account", "specified account_id", "owner"]))
PYRepository: recoupable/docs
Length of output: 943
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- source references to account_id and stream chats ---'
rg -n "account_id|chatId|ChatStreamErrorResponse|Access denied to specified account_id|startIndex" --glob '!api-reference/openapi/research.json' .
echo '--- route files candidates ---'
fd -t f '.*' . | rg 'api|chat|stream|route' | head -200Repository: recoupable/docs
Length of output: 29528
🌐 Web query:
mcp.mdx account_id stream supported endpoints org API keys only
💡 Result:
The phrase "mcp.mdx account_id stream supported endpoints org API keys only" appears to be a fragmented search query or a set of notes regarding technical documentation for Model Context Protocol (MCP) implementations. Various services use the.mdx file extension for their documentation [1][2][3][4][5][6][7][8]. Based on common patterns found in MCP server documentation: 1. MCP Transports and Endpoints: Modern MCP servers frequently use "Streamable HTTP" as a transport mechanism [1][2][9][3]. This is often a single, stateless HTTP endpoint [1][9][3]. 2. Authentication (API Keys/Org IDs): Many implementations require an API key passed in the request header (e.g., X-API-Key or Authorization: Bearer <key>) [1][2][9][3][10]. For services that manage multiple organizations, some require an additional header (e.g., Nash-Org-Id) to specify the target organization [11]. However, as noted in some documentation (e.g., ModuleX), certain implementations are designed to authenticate via the API key alone because the server identifies the organization from the endpoint URL or the key itself, explicitly advising against adding an X-Organization-ID header in those cases [9]. 3. Stream vs. Batch: While MCP is a protocol, the term "stream" in this context often refers to the Streamable HTTP transport [1][2][3] or specific streaming API capabilities [12][13][14]. 4. Supported Endpoints: These are typically defined by the server's capabilities (e.g., list_tools, tools/call, resources/read) as per the MCP specification, rather than standard REST endpoints [1][9][8]. If you are developing or configuring an MCP integration, you should refer to the specific documentation provided by your service provider (often found at /mcp.mdx or similar paths in their documentation repositories) to confirm if they require a specific Org ID header or if the API key is sufficient for authentication [1][9][11].
Citations:
- 1: https://github.com/IcelandicIcecream/aphex/blob/main/docs/aphex-docs/content/docs/mcp.mdx
- 2: https://github.com/genfeedai/genfeed.ai/blob/master/apps/docs/content/api-reference/mcp.mdx
- 3: https://github.com/kensaurus/mushi-mushi/blob/master/apps/docs/content/sdks/mcp.mdx
- 4: https://github.com/langchain-ai/docs/blob/main/src/oss/langchain/mcp.mdx
- 5: https://docs.vapi.ai/cli/mcp.mdx
- 6: https://github.com/mintlify/docs/blob/59297146/mcp.mdx
- 7: https://launchdarkly.com/docs/home/getting-started/mcp.mdx
- 8: https://github.com/cloudflare/cloudflare-docs/blob/production/src/content/docs/ai-search/usage/mcp.mdx
- 9: https://docs.modulex.dev/api-reference/mcp/connect-a-client
- 10: https://docs.streamkap.com/mcp-server
- 11: https://docs.usenash.com/reference/generating-api-key-and-org-id
- 12: https://developer-specs.company-information.service.gov.uk/streaming-api/guides/authentication
- 13: https://github.com/antoinezambelli/forge/blob/main/docs/BACKEND_SETUP.md
- 14: https://developers.kloudless.com/docs/v2/activity
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Focused source/content check without running repository code or building.
echo '--- mcp.mdx account_id context ---'
sed -n '60,90p' mcp.mdx 2>/dev/null || true
echo '--- authentication.mdx account_id context ---'
sed -n '68,105p' authentication.mdx 2>/dev/null || true
echo '--- workflow stream generated docs ---'
for f in api-reference/chat/workflow-stream.mdx api-reference/chat/workflow.mdx; do
if [ -f "$f" ]; then
echo "--- $f ---"
sed -n '1,220p' "$f"
fi
doneRepository: recoupable/docs
Length of output: 2683
Align the stream auth and account mismatch errors with account_id.
account_id can authorize another account, but the 403 response still says ownership failed and the 400 response only documents invalid startIndex. Update the operation/response text to include malformed account_id behavior and bearer-token limitations for account overrides.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@api-reference/openapi/research.json` around lines 789 - 798, Update the
OpenAPI operation containing the account_id query parameter and its 400/403
response descriptions: document malformed account_id values in the 400 response
alongside invalid startIndex, and revise the 403 stream
authorization/account-mismatch text to describe bearer-token limitations and
denied account overrides rather than ownership failure.
| "x-workflow-stream-tail-index": { | ||
| "description": "Zero-based index of the last chunk known to the stream **at the moment this read was opened**. Because headers are sent before the body, a read that stays open past that point will deliver chunks beyond it — so this is a base for computing absolute positions, not a record of where the response ended. A client resuming precisely should count the chunks it receives on top of this value. Omitted if the runtime cannot report it.", | ||
| "schema": { | ||
| "type": "integer" | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Constrain the zero-based header value.
Line 821 defines a zero-based index. Add "minimum": 0 to the header schema. Without this constraint, the OpenAPI contract accepts negative tail indexes.
Proposed schema constraint
"schema": {
- "type": "integer"
+ "type": "integer",
+ "minimum": 0
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "x-workflow-stream-tail-index": { | |
| "description": "Zero-based index of the last chunk known to the stream **at the moment this read was opened**. Because headers are sent before the body, a read that stays open past that point will deliver chunks beyond it — so this is a base for computing absolute positions, not a record of where the response ended. A client resuming precisely should count the chunks it receives on top of this value. Omitted if the runtime cannot report it.", | |
| "schema": { | |
| "type": "integer" | |
| } | |
| "x-workflow-stream-tail-index": { | |
| "description": "Zero-based index of the last chunk known to the stream **at the moment this read was opened**. Because headers are sent before the body, a read that stays open past that point will deliver chunks beyond it — so this is a base for computing absolute positions, not a record of where the response ended. A client resuming precisely should count the chunks it receives on top of this value. Omitted if the runtime cannot report it.", | |
| "schema": { | |
| "type": "integer", | |
| "minimum": 0 | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@api-reference/openapi/research.json` around lines 820 - 824, Add a minimum
constraint of 0 to the schema for the x-workflow-stream-tail-index header,
preserving its integer type and existing description so negative zero-based
indexes are rejected.
There was a problem hiding this comment.
2 issues found across 1 file
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="api-reference/openapi/research.json">
<violation number="1" location="api-reference/openapi/research.json:793">
P3: The new account_id parameter documents the 403 override-authorization behavior, but the existing 403/400 error response descriptions aren't updated to mention malformed account_id or bearer-token limitations for account overrides. Consider cross-referencing these error responses so the contract fully reflects the new account_id semantics.</violation>
<violation number="2" location="api-reference/openapi/research.json:794">
P3: The x-workflow-stream-tail-index header is documented as a zero-based index, but its schema only declares `"type": "integer"` without a `"minimum": 0` constraint, so the OpenAPI contract technically allows negative values.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| "in": "query", | ||
| "required": false, | ||
| "description": "Act on a chat belonging to another account. Only honoured for a key that is authorised for that account — an organization key may pass a member account's id; a personal key passing anyone else's is rejected with `403 Access denied to specified account_id`. Omit it to act as the authenticated account.", | ||
| "schema": { |
There was a problem hiding this comment.
P3: The x-workflow-stream-tail-index header is documented as a zero-based index, but its schema only declares "type": "integer" without a "minimum": 0 constraint, so the OpenAPI contract technically allows negative values.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At api-reference/openapi/research.json, line 794:
<comment>The x-workflow-stream-tail-index header is documented as a zero-based index, but its schema only declares `"type": "integer"` without a `"minimum": 0` constraint, so the OpenAPI contract technically allows negative values.</comment>
<file context>
@@ -786,6 +786,16 @@
+ "in": "query",
+ "required": false,
+ "description": "Act on a chat belonging to another account. Only honoured for a key that is authorised for that account — an organization key may pass a member account's id; a personal key passing anyone else's is rejected with `403 Access denied to specified account_id`. Omit it to act as the authenticated account.",
+ "schema": {
+ "type": "string",
+ "format": "uuid"
</file context>
| "name": "account_id", | ||
| "in": "query", | ||
| "required": false, | ||
| "description": "Act on a chat belonging to another account. Only honoured for a key that is authorised for that account — an organization key may pass a member account's id; a personal key passing anyone else's is rejected with `403 Access denied to specified account_id`. Omit it to act as the authenticated account.", |
There was a problem hiding this comment.
P3: The new account_id parameter documents the 403 override-authorization behavior, but the existing 403/400 error response descriptions aren't updated to mention malformed account_id or bearer-token limitations for account overrides. Consider cross-referencing these error responses so the contract fully reflects the new account_id semantics.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At api-reference/openapi/research.json, line 793:
<comment>The new account_id parameter documents the 403 override-authorization behavior, but the existing 403/400 error response descriptions aren't updated to mention malformed account_id or bearer-token limitations for account overrides. Consider cross-referencing these error responses so the contract fully reflects the new account_id semantics.</comment>
<file context>
@@ -786,6 +786,16 @@
+ "name": "account_id",
+ "in": "query",
+ "required": false,
+ "description": "Act on a chat belonging to another account. Only honoured for a key that is authorised for that account — an organization key may pass a member account's id; a personal key passing anyone else's is rejected with `403 Access denied to specified account_id`. Omit it to act as the authenticated account.",
+ "schema": {
+ "type": "string",
</file context>
Row 4 of chat#1923. Three drifts between the published contract and the shipped route, all found by live preview testing of api#809.
1. The error schema was wrong for every 4xx
ChatStreamErrorResponsedeclared{ status, message }withmessagerequired. The route returnserror, plusmissing_fieldson validation failures:{"status":"error","missing_fields":["startIndex"],"error":"startIndex must be a non-negative integer"} {"status":"error","error":"Forbidden"}Both observed live. The schema is pre-existing and backs 401/403/404 as well as the 400 — so every documented error body on this endpoint was inaccurate, not just the one added recently.
2.
account_idwas undocumentedThe admin override shipped in api#809. Documented with the authorisation rule, because the interesting part is that it is validated, not trusted: an organization key may pass a member account's id, a personal key passing anyone else's gets
403 Access denied to specified account_id. Verified live on both paths.3.
x-workflow-stream-tail-indexwas undocumentedIt ships on the 200. The description states the semantic explicitly, because getting it wrong cost a round of rework downstream:
Concretely: a read advertising
tail: 9went on to deliver 22 chunks. Headers are sent before the body, so no header can ever report where a stream ended. A client resuming precisely must count what it receives on top of this value — which is what chat#1924 ended up doing.Verification
[chatId(path), account_id(query), startIndex(query)]; 200 headers to[x-workflow-run-id, x-workflow-stream-tail-index]; error schema requires[status, error].25 insertions, 2 deletions— the 2 deletions are themessagefield being corrected toerror.research.jsondoes not round-trip throughjson.dumpsbyte-for-byte, so a rewrite would bury the change in a spurious diff.Local Mintlify render check to follow before merge.
Refs chat#1923
🤖 Generated with Claude Code
Summary by cubic
Aligns the chat resume-stream OpenAPI docs with the actual route to remove contract drift. Addresses chat#1923.
error, removemessage, add optionalmissing_fieldsfor 400 validation errors (applies to 400/401/403/404).account_idoverride and auth rules (org keys can pass member IDs; unauthorized IDs return 403).x-workflow-stream-tail-indexand clarify it reflects the tail at read open; clients should offset from this value.Written for commit 2434f0d. Summary will update on new commits.