Skip to content

Content-Type: application/json sent with empty body causes server-side JSON parsing errors #40

@doomerhunter

Description

@doomerhunter

Description

The SDK sends Content-Type: application/json header even when no body is present (e.g., POST requests with no payload). This causes servers with JSON body parsers to fail because an empty string is not valid JSON.

Reproduction

Any POST/PUT/PATCH endpoint that doesn't require a request body:

from opencode_ai import AsyncOpencode

client = AsyncOpencode(base_url="http://localhost:3456")
await client.session.create()  # No body parameters

HTTP Request Sent

POST /session HTTP/1.1
Host: localhost:3456
Content-Length: 0
Content-Type: application/json
Accept: application/json
...

Server Response

HTTP/1.1 500 Internal Server Error
Content-Type: application/json

{"name":"UnknownError","data":{"message":"Error: Malformed JSON in request body\n    at <anonymous> (../../node_modules/.bun/hono@4.10.7/node_modules/hono/dist/validator/validator.js:21:21)\n    at processTicksAndRejections (native:7:39)"}}

The server sees Content-Type: application/json, attempts to parse the body as JSON, but fails because the body is empty.

Expected Behavior

When no body content is provided, the SDK should not send Content-Type: application/json. The header should only be present when there's actual JSON content to parse.

POST /session HTTP/1.1
Host: localhost:3456
Content-Length: 0
Accept: application/json

Root Cause

In _base_client.py, the default_headers property unconditionally includes Content-Type: application/json:

@property
def default_headers(self) -> dict[str, str | Omit]:
    return {
        "Accept": "application/json",
        "Content-Type": "application/json",  # Always set
        ...
    }

The _build_request method only removes this header for GET requests:

is_body_allowed = options.method.lower() != "get"

if is_body_allowed:
    if isinstance(json_data, bytes):
        kwargs["content"] = json_data
    else:
        kwargs["json"] = json_data if is_given(json_data) else None
    kwargs["files"] = files
else:
    headers.pop("Content-Type", None)  # Only removed for GET

Proposed Fix

Also remove Content-Type when the body is empty for non-GET requests:

if is_body_allowed:
    if isinstance(json_data, bytes):
        kwargs["content"] = json_data
    else:
        kwargs["json"] = json_data if is_given(json_data) else None
    kwargs["files"] = files

    # Remove Content-Type if there's no actual body content
    has_body = isinstance(json_data, bytes) or is_given(json_data) or files
    if not has_body:
        headers.pop("Content-Type", None)
else:
    headers.pop("Content-Type", None)

Workaround

Users can manually omit the header:

from opencode_ai import Omit

await client.session.create(extra_headers={"Content-Type": Omit()})

But this shouldn't be necessary for standard API usage.

Environment

  • SDK: Stainless-generated Python client
  • Python: 3.13.2
  • Server: Hono (Node.js) with JSON body validation

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions