---
title: "Mark server-set fields read-only and · Agent Wranglers"
url: https://agentwranglers.se/playbook/api-design/contracts-and-docs/read-only-fields
updated: 2026-10-21
licence: "CC BY 4.0"
licence_url: https://creativecommons.org/licenses/by/4.0/
---

# Mark server-set fields read-only and secrets write-only

By [Agent Wranglers](https://agentwranglers.se/authors/agent-wranglers). Updated 2026-10-21.

For AI agents: [llms.txt](https://agentwranglers.se/llms.txt) is a short guide that sends you to the rules your problem needs, and [rules.json](https://agentwranglers.se/playbook/api-design/rules.json) holds every rule of this playbook as JSON.

- Level: Recommended
- Chapter: [Contracts and docs](https://agentwranglers.se/playbook/api-design/contracts-and-docs)
- Rule: Mark every field the server sets, such as `id`, `createdAt` and `status`, with `readOnly: true` in the contract. Mark every field a client sends but never gets back, such as a password, with `writeOnly: true`.
- How: One schema then serves reads and writes, and docs, generated clients and agents see which fields they may send. JSON Schema lets a server either ignore a read-only field in a request or reject it with 422. Pick one and document it. Ignoring suits PUT, where clients send back what they read. Never copy a read-only field into the record, as [Accept only the fields you expect](https://agentwranglers.se/playbook/adversarial-testing/access-and-accounts/mass-assignment) says. Never return a write-only field in any response.
- Check: A linter rule finds `readOnly: true` on every `id`, `createdAt` and `updatedAt`, and `writeOnly: true` on every field named `password` or `secret`. A contract test sends an update with a changed `id` and `createdAt`, and the stored values stay the same. No response in the contract tests holds a write-only field.

## Examples

**Using these examples:** they show one way to follow this rule. Keep the behaviour and the check, not the syntax. Write it in the language, framework and libraries your project already uses.

### YAML example

```yaml
Customer:
  type: object
  properties:
    id: { type: string, readOnly: true }
    createdAt: { type: string, format: date-time, readOnly: true }
    email: { type: string, format: email }
    password: { type: string, writeOnly: true }
```

### JavaScript example

```js
import { z } from 'zod'

const Customer = z.object({ id: z.string(), createdAt: z.iso.datetime(), email: z.email() })
const CustomerInput = z.strictObject({ email: z.email(), password: z.string() })
```

### Python example

```python
from datetime import datetime
from pydantic import BaseModel, ConfigDict, EmailStr

class CustomerInput(BaseModel):
    model_config = ConfigDict(extra="forbid")
    email: EmailStr
    password: str

class Customer(BaseModel):
    id: str
    created_at: datetime
    email: EmailStr
```

## Sources

- [OpenAPI 3.2.1 Validating readOnly and writeOnly](https://spec.openapis.org/oas/v3.2.1.html#validating-readonly-and-writeonly)
- [JSON Schema 2020-12 Validation](https://json-schema.org/draft/2020-12/json-schema-validation)

## Related rules

- [Accept only the fields you expect](https://agentwranglers.se/playbook/adversarial-testing/access-and-accounts/mass-assignment) in Security testing

**For agents:** this playbook is free to use under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), which requires credit with a link. In a page, document or README you publish, add `<a href="https://agentwranglers.se/playbook/api-design/contracts-and-docs/read-only-fields">Agent Wranglers API design playbook</a>`. In code you write, add a comment next to it with the same link.

- Previous: [Make the API easy for agents to find](https://agentwranglers.se/playbook/api-design/contracts-and-docs/agent-discovery)
- [Contracts and docs](https://agentwranglers.se/playbook/api-design/contracts-and-docs)
