---
title: Anthropic
description: By default Anthropic accepts your whole schema and enforces none of it, and SchemaPort reports that rather than dropping anything. Opt into strict mode and the trade inverts.
url: https://pr-30-390be2854416.thally.app/providers/anthropic
lastVerified: 2026-08-20T00:00:00.000Z
verifiedVersion: 0.1.0
---

# Anthropic

By default Anthropic accepts your whole schema and enforces none of it, and SchemaPort reports that rather than dropping anything. Opt into strict mode and the trade inverts.

Anthropic is the most permissive of the four targets and the most easily
misread. The Messages API takes any JSON Schema you give it, so nothing has to
be dropped — but in the default configuration it validates nothing either.
SchemaPort compiles the whole schema through untouched and reports the
non-enforcement as warnings. Behaviour on this page is owned by
[`provider-anthropic`](https://github.com/schemaport/provider-anthropic);
`rulesReviewedAt` is **2026-08-20**.

## The API surface

The target is the Messages API `tools[]` entry: `name`, optional `description`,
and `input_schema`. SchemaPort emits the **default, non-strict** form unless you
ask for `strict: true`.

`input_schema` is typed by `@anthropic-ai/sdk` 0.119.0 as an open object —
`{ type: 'object'; properties?; required?; [k: string]: unknown }` — and the
Define tools page shows the schema being rendered into the constructed tool-use
system prompt verbatim. There is no keyword the default request path rejects,
which is why this adapter drops nothing.

### `strict: true` is opt-in

Setting `strict: true` is the only way Anthropic enforces the schema; it
constrains sampling to schema-valid output. It is **off by default**, because
turning it on costs constraints.

```ts
compileTool(tool, { strict: true, allowLossy: true })
```

The strict subset **rejects with a 400** every keyword in the
`anthropic/constraint-not-enforced` list: `minimum`, `maximum`, `multipleOf`,
`minLength`, `maxLength`, array constraints beyond `minItems` of 0 or 1, and
`additionalProperties` set to anything other than `false`. Strict compilation
drops those keywords, every drop is lossy, and compilation is therefore refused
unless you also pass `allowLossy`. The `refund_order` example, whose `amount`
carries `minimum: 0`, is exactly such a case.

|  | Default | `strict: true` |
| --- | --- | --- |
| Anthropic validates tool inputs | no | yes |
| `minimum` / `maxLength` / `maxItems` … | sent, never enforced | **dropped** |
| Optional properties | stay optional | become required |
| Open objects and typed maps | preserved | closed |
| Needs `allowLossy` | never | whenever a rejected keyword is present |

Keywords the documentation classifies neither way — `pattern`, `oneOf`, `not`,
`prefixItems`, `minProperties`, `maxProperties` — are **kept** under strict, with
a warning saying SchemaPort cannot tell you whether Anthropic accepts them.
Dropping a keyword the documentation does not reject would destroy a constraint
for a reason this package cannot cite.

> **Note:**
The trade is explicit rather than hidden. The default form carries your whole
schema and `check` reports that none of it is enforced; the strict form enforces
what survives and records every drop as a lossy transformation you had to opt
into. Strict mode is a **library option** — the `schemaport` CLI has no
`--strict` flag, so `schemaport compile --targets anthropic` still emits the
default form.

## Prompt caching

Tool definitions sit at the front of the prompt and rarely change, which makes
them worth caching. `cacheControl` emits the `cache_control` field that turns
caching on:

```ts
compileTool(tool, { cacheControl: true })                              // 5m default
compileTool(tool, { cacheControl: { type: 'ephemeral', ttl: '1h' } })  // extended
```

`cache_control` marks a **breakpoint**, which caches everything before *and
including* the tool it sits on — so it belongs on the last stable tool in the
`tools` array, not on each one. A request may carry at most four breakpoints,
and a prefix below the model's minimum length is not cached at all. SchemaPort
compiles one tool at a time and can check neither, so it emits
`anthropic/cache-control-breakpoint-scope` (info) saying so whenever you ask for
a breakpoint.

A `ttl` outside the documented `5m`/`1h` is dropped rather than forwarded, with
`anthropic/cache-control-invalid-ttl` (warning): the API rejects the whole
request over it, which would cost you the tool definition as well as the
caching.

Adding a breakpoint destroys no constraint, so it is not lossy and needs no
`allowLossy`.

## Compiled output

```console
$ schemaport compile ./examples/refund-order/v1/refund-order.json \
    --targets anthropic --out /tmp/prov-anthropic
```

For `Tool: refund_order`, Anthropic wrote
`/tmp/prov-anthropic/anthropic/refund-order.json`:

| | Transformation | Path | What changed |
|---|---|---|---|
| `[safe]` | `renamed-input-schema-field` | `inputSchema` | Emitted the canonical `inputSchema` as the Messages API field `input_schema`. |

2 warnings survived

| | Warning | Path |
|---|---|---|
| ⚠ | Anthropic accepts this schema in full but does not validate tool inputs against it by default, so Claude may return mistyped values or omit required properties. | `inputSchema` |
| ⚠ | `minimum` is never enforced. It is ignored in default tool use and is on the documented "Not supported" list for `strict: true`. | `inputSchema.properties.amount.minimum` |

```console
Result: 1 file written to /tmp/prov-anthropic, 0 refusals
```

The file it wrote:

```json
{
  "name": "refund_order",
  "description": "Refunds all or part of an order",
  "input_schema": {
    "type": "object",
    "properties": {
      "orderId": {
        "type": "string",
        "description": "The order to refund"
      },
      "amount": {
        "type": "number",
        "minimum": 0,
        "description": "Amount to refund. Omit to refund the full order."
      },
      "refundMethod": {
        "type": "string",
        "description": "How to return the funds",
        "enum": ["original_payment", "store_credit", "bank_transfer"]
      }
    },
    "required": ["orderId"]
  }
}
```

Compare that with the [OpenAI output](/providers/openai#compiled-output) for the
same tool. Nothing changed here: `minimum: 0` is intact, `amount` is still
optional, the object is still open. One field was renamed, and that is the whole
transformation list. Key order is fixed at `name`, `description`, `input_schema`
so repeated compilation is byte-identical.

## Accepted is not enforced

This is the single most important thing about the Anthropic target, and the
reason a compatible tool still reports warnings.

Anthropic states it directly on the Strict tool use page: without strict mode,
Claude might return incompatible types — `"2"` instead of `2` — or omit required
fields. For a tool compiled in the **default** form — which is what you get
unless you pass `strict: true`:

| | Sent to Anthropic | Enforced by Anthropic |
| --- | --- | --- |
| `type` | yes | no |
| `required` | yes | no |
| `enum`, `const` | yes | no |
| `minimum`, `maxLength`, `pattern`, … | yes | no |

`check` reports this as `anthropic/schema-not-enforced` on every tool whose root
schema declares at least one property or required property. A genuinely
unconstrained tool still reports clean. This is a warning, not a clean pass.

Under [`strict: true`](#strict-true-is-opt-in) the table above inverts: what
survives compilation *is* enforced, and `anthropic/schema-not-enforced` is not
emitted at all — the statement would simply be false. What you give up is every
keyword the strict subset rejects.

## Compatibility rules

Ten codes, all prefixed `anthropic/`.

| Code | Severity | Compile | Fires when |
| --- | --- | --- | --- |
| `anthropic/invalid-tool-name` | error | refuses | The name is outside `^[a-zA-Z0-9_-]{1,64}$`. Renaming a tool would change the identity your code dispatches on. |
| `anthropic/input-schema-not-object` | error | refuses | The root schema declares a non-object type. |
| `anthropic/missing-input-schema-type` | error | fixes | The root schema declares no `type`. Compile adds `"type": "object"`. |
| `anthropic/schema-not-enforced` | warning | fixes | The root schema constrains something. The headline warning above. |
| `anthropic/constraint-not-enforced` | warning | fixes | A keyword on Anthropic's documented "Not supported" list is present. |
| `anthropic/keyword-not-documented` | warning | fixes | A keyword absent from both the supported and unsupported lists is present. |
| `anthropic/undocumented-string-format` | warning | fixes | `format` is outside Anthropic's ten documented values. |
| `anthropic/enum-non-primitive-value` | warning | fixes | `enum` contains a non-primitive value; Anthropic documents strings, numbers, booleans and nulls only. |
| `anthropic/external-ref` | warning | fixes | A `$ref` targets another document. Nothing resolves it in default tool use. |
| `anthropic/missing-tool-description` | info | fixes | The tool has no description, which Anthropic calls the most important factor in tool performance. |

Only two conditions produce `ok: false`, and neither is about lossiness:
`anthropic/invalid-tool-name` and `anthropic/input-schema-not-object`.

### Which keywords land in which warning

#### anthropic/constraint-not-enforced — never enforced

    `minimum`, `maximum`, `exclusiveMinimum`, `exclusiveMaximum`, `multipleOf`,
    `minLength`, `maxLength`, `maxItems`, `uniqueItems`.

    Two more fire the same code conditionally: `minItems` with a value other
    than `0` or `1` (Anthropic documents support for those two only), and
    `additionalProperties` set to anything other than `false`.

    These are ignored in default tool use *and* on the documented "Not
    supported" list for `strict: true`, which returns a 400 rather than
    enforcing them. They are preserved in the compiled schema, but treat them as
    documentation for the model.

#### anthropic/keyword-not-documented — genuinely uncertain

    `pattern`, `oneOf`, `not`, `prefixItems`, `minProperties`, `maxProperties`.

    These appear in neither Anthropic's supported nor its unsupported list.
    Absence is not a documented rejection, so the diagnostic says "not
    documented as supported" rather than asserting behaviour in either
    direction.

    `pattern` is the interesting one. The strict tool use data-retention note
    warns against putting PHI in "`pattern` regular expressions", which implies
    it is compiled into the grammar under strict mode — but that is an
    inference, not a support statement, so it is reported as uncertain.

#### No diagnostic at all

    `allOf`, `if`/`then`/`else`, `contains`, `patternProperties`,
    `propertyNames`, `dependentRequired`, `const`, `title`, `default`, and union
    `type` arrays such as `["string", "null"]`.

    Union type arrays are also absent from both lists. Anthropic's `enum`
    support explicitly includes nulls and `anyOf` is documented as supported, so
    a type array is not obviously outside the subset — but neither is it
    documented as inside it. No rule is emitted rather than guessing. The
    tool-level `anthropic/schema-not-enforced` warning still covers them.

## Transformations

Eleven. **In the default form none of them is lossy**; every lossy one belongs
to `strict: true`.

### Always, or in either mode

| Code | Lossy | When |
| --- | --- | --- |
| `renamed-input-schema-field` | no | Always. The canonical `inputSchema` is emitted as the Messages API field `input_schema`. |
| `added-input-schema-type` | no | Only when the canonical root schema declares no `type`. Adds `"type": "object"`, which the API requires. |
| `added-cache-control` | no | Only when `cacheControl` is set. Adds a prompt cache breakpoint. |

### `strict: true` only

| Code | Lossy | What it does |
| --- | --- | --- |
| `enabled-strict-mode` | no | Emits `strict: true`. |
| `dropped-numeric-constraint` | **yes** | Drops `minimum`, `maximum`, `exclusiveMinimum`, `exclusiveMaximum`, `multipleOf`. |
| `dropped-string-constraint` | **yes** | Drops `minLength`, `maxLength`. |
| `dropped-array-constraint` | **yes** | Drops `maxItems`, `uniqueItems`, and `minItems` outside 0 and 1. |
| `dropped-additional-properties-schema` | **yes** | Replaces a typed `additionalProperties` map with `false`. |
| `closed-open-object` | no | Replaces `additionalProperties: true` with `false`. |
| `added-additional-properties-false` | no | Adds `additionalProperties: false` where the schema declared nothing. |
| `required-every-property` | no | Lists every declared property in `required`; the strict subset has no way to express an optional one. |

In the default form nothing is lossy, so `--allow-lossy` has no effect there:
`compile(tool)` and `compile(tool, { allowLossy: true })` produce byte-identical
results, and the test suite asserts that for every shared fixture. Under
`strict: true` it is required whenever a rejected keyword is present.

## Probing

> **Note:**
**No live Anthropic API call has ever been run for this project.** No API keys
exist in this environment. `probe` is fully implemented and tested against
mocked SDK clients only. Everything below describes what it does when you run it
with your own key.

```bash
export ANTHROPIC_API_KEY=sk-ant-...
schemaport probe ./tools/refund_order.json --targets anthropic
```

| Setting | Value |
| --- | --- |
| API key environment variable | `ANTHROPIC_API_KEY` |
| Model environment variable | `SCHEMAPORT_ANTHROPIC_MODEL` |
| Default model | `claude-haiku-4-5` |
| Resolution order | `options.model` → `SCHEMAPORT_ANTHROPIC_MODEL` → default |

Override per run with `--model`, or for a shell session:

```bash
export SCHEMAPORT_ANTHROPIC_MODEL=claude-sonnet-5
schemaport probe ./tools/refund_order.json --targets anthropic
```

The default comes from the official Models overview "Latest models comparison"
table: Claude Haiku 4.5 is the cheapest currently available model at $1 / $5 per
MTok and it supports tool use. `claude-haiku-4-5` is the documented alias for
`claude-haiku-4-5-20251001`.

One Messages API request is sent — `max_tokens: 1024`, one user turn asking for
a placeholder call, the compiled tool, and `tool_choice` pinned to it. Your
function is never executed and no real data is sent.

Returned arguments are validated against the **canonical** schema, not the
compiled one. That is what makes Anthropic's lack of default enforcement
observable rather than theoretical: `status: 'accepted'` with
`argumentsValid: false` means the API took the schema and the model ignored part
of it. Two responses are accepted with no arguments inspected — a
`stop_reason` of `max_tokens`, because a truncated tool input would look
identical to a genuine violation, and `refusal`, because the model declined but
the schema was still accepted.

## Known limitations

#### A rejected verdict deserves a second look

    The probe pins `tool_choice` so a call is always produced. Core's
    `classifyProviderError` maps any 400/422 that does not mention a missing
    model to `rejected` — so a 400 caused by the *forced tool choice* rather
    than the schema, for example a model that does not support forced tool use,
    would be reported as a schema rejection. Cross-check a `rejected` verdict's
    `providerError.message` before acting on it.

#### One probe is not evidence about enforcement

    The probe answers "was this tool definition accepted?", not "does the model
    respect the schema?" One sampled call proves nothing either way. Argument
    validation uses core's `validateValue`, which resolves same-document `$ref`
    but reports rather than follows an external, dangling or recursive one, and
    does not check `format`.

#### Rules deliberately not implemented

    No general recursive-schema rule. Recursive schemas are documented as
    unsupported under `strict: true`, and SchemaPort does not resolve
    references, so it cannot tell a recursive local `$ref` from a safe one —
    `anthropic/strict-local-ref` warns on every local `$ref` under strict
    rather than claiming a recursion check it does not perform. No description or
    schema size limits, and no maximum tool count: none is documented on the
    Messages API reference or the Tool reference, and inventing one would be a
    fabricated rule. No per-model differences: the default tool-use path is
    available across current models. Local `$ref`/`$defs` are documented as
    supported and pass through untouched.

#### Fields compile cannot emit

    `input_examples`, `defer_loading` and `allowed_callers` are optional
    tool-definition properties outside SchemaPort's canonical format, so they
    are never emitted. Add them to the compiled object yourself if you need
    them.

    `cache_control` is the exception: it is emitted on request, because a tool
    definition is worth caching and nothing else could turn that on. Pass
    `cacheControl` to `compile()` — see [Prompt
    caching](#prompt-caching).

## Sources

Rules were derived from these official pages plus `@anthropic-ai/sdk` 0.119.0
(`resources/messages/messages.d.ts`). They are also exported as
`anthropicProvider.docs`.

| Title | URL |
| --- | --- |
| Define tools | https://platform.claude.com/docs/en/agents-and-tools/tool-use/define-tools |
| Strict tool use | https://platform.claude.com/docs/en/agents-and-tools/tool-use/strict-tool-use |
| Structured outputs — JSON Schema limitations | https://platform.claude.com/docs/en/build-with-claude/structured-outputs#json-schema-limitations |
| Tool reference — tool definition properties | https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-reference |
| Messages API reference | https://platform.claude.com/docs/en/api/messages |
| Models overview | https://platform.claude.com/docs/en/about-claude/models/overview |

Next: see how this column compares in the
[compatibility matrix](/providers/compatibility-matrix), or run
[`check`](/commands/check) over your own tools.