Skip to content

docs(chat): reconcile the resume-stream contract with what the route returns - #287

Open
sweetmantech wants to merge 1 commit into
mainfrom
docs/chat-stream-contract-reconcile
Open

docs(chat): reconcile the resume-stream contract with what the route returns#287
sweetmantech wants to merge 1 commit into
mainfrom
docs/chat-stream-contract-reconcile

Conversation

@sweetmantech

@sweetmantech sweetmantech commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

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

ChatStreamErrorResponse declared { status, message } with message required. The route returns error, plus missing_fields on 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_id was undocumented

The 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-index was undocumented

It ships on the 200. The description states the semantic explicitly, because getting it wrong cost a round of rework downstream:

the last chunk known to the stream at the moment this read was opened… 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.

Concretely: a read advertising tail: 9 went 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

  • File parses; params resolve to [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 the message field being corrected to error.
  • Applied via anchored text edits: research.json does not round-trip through json.dumps byte-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.

  • Bug Fixes
    • Error schema: require error, remove message, add optional missing_fields for 400 validation errors (applies to 400/401/403/404).
    • Query param: document account_id override and auth rules (org keys can pass member IDs; unauthorized IDs return 403).
    • Response headers: add x-workflow-stream-tail-index and clarify it reflects the tail at read open; clients should offset from this value.

Written for commit 2434f0d. Summary will update on new commits.

Review in cubic

…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>
@cursor

cursor Bot commented Aug 3, 2026

Copy link
Copy Markdown

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.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The OpenAPI documentation adds an optional account_id query parameter and the x-workflow-stream-tail-index response header for chat streams. It also updates ChatStreamErrorResponse to use error and optionally report missing_fields.

Changes

Chat stream API contract

Layer / File(s) Summary
Stream access and metadata
api-reference/openapi/research.json
The stream endpoint documents optional UUID-based account_id access and the x-workflow-stream-tail-index response header.
Stream error schema
api-reference/openapi/research.json
ChatStreamErrorResponse now requires error and supports optional missing_fields validation details.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related issues

  • recoupable/chat#1923 — Tracks matching contract changes for the chat stream endpoint, including account_id, the stream tail index, and error fields.

Possibly related PRs

  • recoupable/docs#286 — Updates the same chat stream OpenAPI documentation with related query and response details.

Suggested reviewers: arpitgupta1214

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the documentation update that aligns the resume-stream contract with the route behavior.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch docs/chat-stream-contract-reconcile

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.

❤️ Share

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

@coderabbitai coderabbitai 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.

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

📥 Commits

Reviewing files that changed from the base of the PR and between f24a953 and 2434f0d.

📒 Files selected for processing (1)
  • api-reference/openapi/research.json

Comment on lines +789 to +798
{
"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"
}
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 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.json

Repository: 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"]))
PY

Repository: 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 -200

Repository: 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:


🏁 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
done

Repository: 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.

Comment on lines +820 to +824
"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"
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 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.

Suggested change
"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.

@cubic-dev-ai cubic-dev-ai 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.

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": {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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>

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