Skip to content

ADR 0006: The REST API surface is generated, not hand-written

Status

Accepted, implemented (M4).

Context

The anthropic Python SDK exposes 131 distinct endpoint-backed methods across messages, messages.batches, models, and an extensive beta namespace (agents, sessions, environments, vaults, memory_stores, deployments, files, skills, webhooks, and more), plus six local helpers with no corresponding HTTP endpoint (messages.stream/parse, beta.messages.stream/parse/tool_runner, beta.webhooks.unwrap).

Research during planning also found that Anthropic's own ant CLI already covers this entire surface — it's generated from the same Stainless OpenAPI spec as the Python SDK (identical openapi_spec_hash and config_hash at the time of research). A hand-written claudeloop api ... command tree would, at best, duplicate ant, and — because SDK methods get added with every model/API release — would start silently missing new methods the moment anyone stopped tracking upstream changes by hand.

Decision

claudeloop api ... will be generated by walking anthropic's resource class tree via introspection (inspect.signature over the cached_property descriptors under anthropic.resources, not a live client instance — so no credentials are needed at import time), binding each discovered method to a Typer command, and gating the whole thing with a CI test that enumerates the SDK surface and fails the build the moment a method exists in the SDK with no corresponding generated command.

Consequences

  • "No gaps" becomes an enforced claim, not an aspirational one. The drift test also asserts the discovered method count against a committed baseline, so removals (an SDK method disappearing) are caught too, not just additions.
  • Path and scalar parameters become real typed Typer options; the request body is accepted as --json / --json-file with @path file inlining rather than flattening every nested TypedDict into individual flags — ant reaches the same conclusion independently, using relaxed-YAML structured flags for the same reason.
  • --raw / --stream modifiers select the with_raw_response / with_streaming_response SDK variants; list methods get --max-items for auto-pagination; --provider selects among the alternate SDK clients (AnthropicBedrock, AnthropicVertex, AnthropicAWS, AnthropicGoogleCloud, AnthropicFoundry) — the binder must reflect that only AnthropicAWS, AnthropicGoogleCloud, and AnthropicFoundry carry the SDK's full resource tree; the others (AnthropicBedrock, AnthropicBedrockMantle, AnthropicVertex) expose Messages and Beta only, and offering commands that would simply fail for those providers is worse than not generating them.
  • The six local helpers with no HTTP endpoint are explicitly enumerated in the drift test as either bound to a hand-written command or deliberately exempted, so none of them can be silently forgotten the way a purely automatic surface-diff might miss them.