How to Distinguish Between Null and Missing Fields in Anthropic API Responses

Use the model_fields_set property (or __fields_set__ in Pydantic v1) to check if a field was present in the API response, and hasattr() or direct attribute access to detect explicit null values mapped to None.

The Anthropic Python SDK materializes JSON API responses into Pydantic BaseModel subclasses, making it essential to distinguish between null and missing fields in API responses when handling optional parameters or partial updates. Understanding these semantics prevents bugs when the API explicitly returns null versus omitting a key entirely.

How the Anthropic SDK Represents Null vs Missing Fields

When parsing API responses, the SDK maintains two distinct concepts:

Concept SDK Representation Detection Method
Field present with value null Attribute exists with value None hasattr(obj, "field") returns True
Field completely absent Attribute not in __fields_set__, access raises AttributeError "field" in obj.model_fields_set returns False

This distinction is critical for endpoints that treat explicit null as "clear this value" versus omitted fields as "no change."

Tracking Missing Fields in Pydantic Models

The NotGiven Sentinel for Requests

When constructing requests, the SDK uses a NotGiven sentinel defined in src/anthropic/_types.py to distinguish between omitting a parameter and passing None:


# src/anthropic/_types.py – NotGiven definition

# https://github.com/anthropics/anthropic-sdk-python/blob/main/src/anthropic/_types.py#L27-L57

class NotGiven:
    """Distinguishes between an explicit `None` and an omitted argument."""
    def __bool__(self) -> Literal[False]:
        return False
    def __repr__(self) -> str:
        return "NOT_GIVEN"

not_given = NotGiven()

Passing NotGiven (or simply omitting the parameter) causes the request builder to exclude the key entirely, while passing None sends an explicit null in the JSON payload.

The __fields_set__ and model_fields_set Properties

In src/anthropic/_models.py, the SDK provides a compatibility shim that exposes which fields were actually supplied by the API:


# src/anthropic/_models.py – fields-set shim

# https://github.com/anthropics/anthropic-sdk-python/blob/main/src/anthropic/_models.py#L100-L105

@property
def model_fields_set(self) -> set[str]:
    # forwards-compat shim for pydantic v2

    return self.__fields_set__  # type: ignore

The __fields_set__ attribute (Pydantic v1) or model_fields_set property (unified interface) contains a set of field names that were present in the source JSON. Fields not in this set were omitted by the API.

Response Parsing and Field Construction

The transformation from raw HTTP response to model occurs in src/anthropic/_response.py:


# src/anthropic/_response.py – JSON parsing path

# https://github.com/anthropics/anthropic-sdk-python/blob/main/src/anthropic/_response.py#L89-L95

data = response.json()
return self._client._process_response_data(
    data=data,
    cast_to=cast_to,
    response=response,
)

The SDK uses BaseModel.construct() (or equivalent internal methods) to instantiate models without running full validation. This method preserves the distinction between fields provided as None versus fields not provided at all, populating __fields_set__ accordingly.

Practical Techniques to Detect Null vs Missing

Checking for Explicit Null Values

To determine if a field exists but contains None:

if hasattr(message, "stop_reason"):
    # Field was present in JSON (value could be None or a string)

    reason = message.stop_reason  # May be None

    if reason is None:
        print("API explicitly returned null")
    else:
        print("API returned value:", reason)

Inspecting the Field Set

To distinguish between omitted fields and explicit nulls, use model_fields_set:

if "stop_reason" in message.model_fields_set:
    # Field was present – safe to access, even if value is None

    print("Stop reason present:", message.stop_reason)
else:
    # The API never returned this key

    print("Stop reason not provided by API")

Complete Working Example

from anthropic import Anthropic, NotGiven

client = Anthropic()

# ------------------------------------------------------------------

# 1️⃣ Distinguish omitted vs explicit None in a request

# ------------------------------------------------------------------

# Omit the timeout (use API default)

resp1 = client.messages.create(
    model="claude-3-5-sonnet-20240620",
    max_tokens=100,
    temperature=0.5,
    # timeout omitted → NotGiven sentinel

    timeout=NotGiven,
)

# Explicitly ask for *no* timeout (null in JSON)

resp2 = client.messages.create(
    model="claude-3-5-sonnet-20240620",
    max_tokens=100,
    temperature=0.5,
    timeout=None,          # will be sent as `"timeout": null`

)

# ------------------------------------------------------------------

# 2️⃣ Detect null vs missing in a response model

# ------------------------------------------------------------------

msg = resp1.content[0]          # Assume a Message object

if "stop_reason" in msg.model_fields_set:
    # `stop_reason` was present – could be None or a string

    print("Stop reason:", msg.stop_reason)
else:
    # The API never returned the key

    print("Stop reason not provided")

Why Distinguishing Null from Missing Matters

Business Logic Precision: Some Anthropic API endpoints treat explicit null as "clear this value" while omitted fields mean "no change" or "use default." Misinterpreting these signals can lead to unintended state modifications.

Backward Compatibility: The NotGiven sentinel (defined in src/anthropic/_types.py) allows client code to remain compatible with API changes. When new optional parameters are added, existing code that omits them (using NotGiven) continues to work without modification, whereas passing None might trigger new validation logic.

Summary

  • Explicit null: Appears as None on the model attribute and is included in model_fields_set (or __fields_set__).
  • Missing fields: Absent from model_fields_set; accessing them raises AttributeError unless checked with hasattr().
  • Request construction: Use NotGiven (from src/anthropic/_types.py) to omit parameters; use None to send explicit null values.
  • Detection pattern: Check "field" in obj.model_fields_set to determine if the API returned the key, then check obj.field is None to detect explicit nulls.

Frequently Asked Questions

How do I check if a field was omitted in an Anthropic API response?

Use the model_fields_set property available on all SDK response models. If the field name is not in this set, the API did not include it in the JSON payload. For example: if "stop_reason" not in message.model_fields_set: print("Field omitted").

What is the difference between None and NotGiven in the Anthropic SDK?

None represents an explicit JSON null value that will be serialized and sent to the API, indicating the field should be cleared or set to null. NotGiven (from src/anthropic/_types.py) is a sentinel class indicating the caller did not provide the argument, causing the SDK to omit the key entirely from the request body.

Can I use hasattr() to detect missing fields in Pydantic models?

Yes, but with caution. hasattr(obj, "field") returns True if the attribute exists on the model, which includes fields explicitly set to None. However, for fields truly missing from the API response (not in __fields_set__), Pydantic models typically raise AttributeError on access, making hasattr() a valid detection mechanism alongside checking model_fields_set.

How does the SDK handle explicit null values in request parameters?

When you pass None to a client method parameter, the SDK serializes this as JSON null in the request body. This is handled in the request building logic where values are processed before transmission. In contrast, passing NotGiven ensures the parameter is excluded from the serialized JSON entirely, allowing the API to apply its default behavior.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →