OpenAPI errors

Fix readOnly and writeOnly ignored by OpenAPI codegen

readOnly and writeOnly tell a schema-aware tool "this field only appears in responses" or "this field only appears in requests" when the same schema is reused for both. Whether a generated SDK actually respects that split depends entirely on the generator — we reproduced two real outcomes: typescript-axios ignores both keywords completely and emits one identical type for request and response, while typescript-fetch partially respects readOnly but still leaks writeOnly fields into the response type. Neither is documented behavior; both only show up by reading the generated code. Reproduced with real tools on 2026-09-21.

How do you reproduce the error?

Take a Message schema reused for both a POST request body and its 200 response, with id marked readOnly (server-assigned) and apiSecret marked writeOnly (client-supplied, never returned):

components:
  schemas:
    Message:
      type: object
      properties:
        id: { type: string, readOnly: true }
        body: { type: string }
        apiSecret: { type: string, writeOnly: true }
      required: [body]

Both swagger-cli validate and redocly lint pass this spec clean — readOnly/writeOnly are valid Schema Object keywords with no validation impact of their own. The real test is the generated code.

typescript-axios (openapi-generator-cli generate -g typescript-axios) produces one interface, used for both directions, with no distinction at all:

export interface Message {
    'id'?: string;
    'body': string;
    'apiSecret'?: string;
}

And the operation signature uses that same type for the request parameter and the response:

createMessage(message: Message, options?: RawAxiosRequestConfig): AxiosPromise<Message>

Nothing stops a caller from writing createMessage({ id: "fake-id", body: "hi" }) — the server-assigned id field is fully settable on the request, and TypeScript sees no problem with it.

typescript-fetch does better on readOnly, but not on writeOnly. Its generated Message model marks id with TypeScript's readonly modifier:

export interface Message {
    readonly id?: string;
    body: string;
    apiSecret?: string;
}

And the request parameter type is Omit<Message, 'id'>id genuinely can't be passed when calling createMessage(). But the response type is still plain Message, apiSecret included:

async createMessageRaw(...): Promise<runtime.ApiResponse<Message>>

apiSecret — a field the server will never send back — shows up in autocomplete on every response, typed as string | undefined, with nothing marking it as request-only.

Why does this happen?

Per the OpenAPI 3.1.1 specification's Schema Object, readOnly "means that it MAY be sent as part of a response but SHOULD NOT be sent as part of the request," and writeOnly is the mirror: "it MAY be sent as part of a request but SHOULD NOT be sent as part of the response." Both are SHOULD NOT, not MUST NOT — the spec itself only asks generators to treat the split as a documentation and validation hint, not a hard contract. Whether a codegen tool derives two separate types (a MessageInput and a MessageOutput) or reuses one is entirely a generator design choice. typescript-fetch's template author built Omit<Message, 'id'> logic for the request side but didn't build the mirror-image Omit<Message, 'apiSecret'> for the response side — an asymmetric implementation, not a documented limitation. typescript-axios's template doesn't attempt either.

How do you fix it?

There's no spec-level fix — readOnly/writeOnly are already correct in the YAML above. The fix is either picking a generator that splits request/response types, or splitting the schemas yourself:

components:
  schemas:
    MessageCreate:
      type: object
      properties:
        body: { type: string }
        apiSecret: { type: string }
      required: [body, apiSecret]
    Message:
      type: object
      properties:
        id: { type: string }
        body: { type: string }
      required: [id, body]
paths:
  /messages:
    post:
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MessageCreate'
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Message'

Two explicit schemas generate two explicit types in every generator we tested, with no dependence on how well a given template handles readOnly/writeOnly. It's more YAML, but it's guaranteed correct rather than generator-dependent.

How different tools react

Tool readOnly (id) on request type writeOnly (apiSecret) on response type
swagger-cli validate / redocly lint No validation impact — keywords are metadata only No validation impact — keywords are metadata only
openapi-generator typescript-fetch Correctly excluded via Omit<Message, 'id'> Not excluded — appears on the response type
openapi-generator typescript-axios Not excluded — same Message type used both ways Not excluded — same Message type used both ways
Split-schema approach (MessageCreate / Message) Excluded by construction, every generator Excluded by construction, every generator

What this means in practice

The typescript-axios case is the more dangerous one in practice, because there's no compiler signal at all — createMessage({ id: someUserSuppliedValue, body }) type-checks and compiles, and the bug is "the server ignores or rejects the field," which surfaces as a runtime behavior mismatch, not a build error. The typescript-fetch case is subtler: the request side is actually safe, but a response's apiSecret field being present in the type (even as optional) can lead a developer to write response.apiSecret && doSomethingWithSecret() code that will never run, because the server never populates it — dead code that looks intentional.

Sourced's SDK compatibility report diffs the generated request and response types against your schema's readOnly/writeOnly markers, so a field that leaks across the request/response boundary shows up before publish rather than after a customer notices it in autocomplete. Create hosted docs from your repo or start a free report to check your own schemas.

FAQ

Does every generator ignore readOnly and writeOnly the same way?

No — we specifically confirmed typescript-axios ignores both entirely and typescript-fetch partially respects readOnly but not writeOnly. Treat this as generator- and even template-version-specific; read the generated model file for your actual generator and version rather than assuming either behavior.

Is splitting into separate Create/Read schemas overkill for a small API?

For a handful of endpoints, reading the generated types once per generator you use is a reasonable check. For an API with many resources or a generator you can't fully audit, the split-schema pattern is the only approach that's correct regardless of the generator's readOnly/writeOnly handling.

Does OpenAPI 3.1 change how readOnly/writeOnly work?

No — readOnly and writeOnly are unchanged between OpenAPI 3.0 and 3.1. Both come from the underlying JSON Schema vocabulary and behave identically in both spec versions; this is purely a codegen-behavior issue, not a spec-version issue.

Will a validator ever catch a writeOnly field leaking into a response?

Not a spec validator — readOnly/writeOnly carry no JSON Schema validation constraint, so a response body that happens to include a writeOnly field wouldn't fail schema validation either. Catching this requires either reading the generated types by hand or a tool that specifically diffs request/response surfaces against schema intent.

Can I mark a property both readOnly and writeOnly?

No — the two are meant to be mutually exclusive per their definitions (response-only vs. request-only), and setting both on the same property is contradictory. If you find yourself wanting that, the property likely needs to be split into two differently named fields, or removed from the shared schema entirely.

What about required combined with readOnly?

A readOnly property can still be listed in required — the spec's guidance is that on the request side, a required readOnly property should be treated as not required (since it can't be sent), while it stays required on the response side. Not every generator implements this distinction cleanly either, so verify it the same way: read the generated code.