IPA-111: Server-Modified Values and Defaults
Server-modified and default values often make it harder to implement state-driven clients. These clients are often unable to tell when their desired state matches the current state for these fields, as the rules by which a server may modify and return values are complex, not public, and not repeatable.
Guidance
Single Owner Fields
Fields must have a single owner, whether that is the client or the server.
- Server-owned fields are fields that are controlled and modified by the service
- Client-owned fields are fields that are controlled by the client
components:
schemas:
Cluster:
type: object
properties:
clusterName:
type: string
stateName:
type: string
readOnly: trueWhy:clusterNameis client-owned and mutable, whilestateNameis server-owned and markedreadOnly: true. Each field has a single, unambiguous owner.For each field in the schema, determine whether the client or the server controls its value.
Confirm fields are not jointly controlled — a field modified by both the client and the server has no single owner.
Flag fields whose ownership is ambiguous or undocumented.
Server-owned fields must be documented as read-only
components:
schemas:
Cluster:
type: object
properties:
stateName:
type: string
description: Current state of the cluster, set by the service.
readOnly: trueWhy:stateNameis controlled by the service and is documented as read-only, signaling to clients that they cannot set it.- Identify the server-owned fields in the schema.
- Confirm each is documented as read-only.
Flag any server-owned field not documented as read-only.
In OpenAPI, server-owned fields must have
readOnly: truecomponents:
schemas:
Cluster:
type: object
properties:
stateName:
type: string
readOnly: trueWhy:The server-owned
stateNamefield setsreadOnly: true, the OpenAPI mechanism for marking a field as not settable by the client.Server-owned fields must not be accepted in request bodies for Create or Update operations
components:
schemas:
Cluster:
type: object
properties:
clusterName:
type: string
stateName:
type: string
readOnly: trueWhy:stateNameis markedreadOnly: true, so it is excluded from Create and Update request bodies and the service ignores any client-supplied value.For each Create or Update operation, inspect the request body schema.
Confirm no server-owned (read-only) field is accepted in the request body.
Flag operations that accept a server-owned field in their request body.
Server-owned fields may be omitted if appropriate to the API
- API Producers should document whether the server-owned field is guaranteed, or if it can be omitted in the response
All fields that are not explicitly documented as server-owned must be considered client-owned
components:
schemas:
Cluster:
type: object
properties:
clusterName:
type: string
stateName:
type: string
readOnly: trueWhy:stateNameis explicitly documented as server-owned viareadOnly: true;clusterNamecarries no such marking and is therefore client-owned.For each field, check whether it is explicitly documented as server-owned.
Treat every field without that documentation as client-owned.
Flag fields whose intended client ownership conflicts with how the service actually treats them.
The server must respect the value (or lack thereof) of all client-owned fields and not modify them
components:
schemas:
Cluster:
type: object
properties:
clusterName:
type: stringWhy:clusterNameis client-owned, so the service stores and returns whatever the client provides without modifying it.Set a client-owned field to a known value via a Create or Update operation.
- Retrieve the resource and inspect the field.
Confirm the service did not modify the value the client sent. Flag any client-owned field the server alters.
The server must always return the same value the client sent (or absence of value) for client-owned fields
components:
schemas:
Cluster:
type: object
properties:
clusterName:
type: stringWhy:For the client-owned
clusterName, the service echoes back exactly the value the client sent, or omits it if the client sent no value.Send a client-owned field with a specific value, and separately send a request omitting it.
- Retrieve the resource after each request.
Confirm the response returns the same value the client sent, and the same absence when no value was sent. Flag discrepancies.
For resources where all fields are server-owned, see read-only resources and read-only singleton resources.
Optional Fields with Server Defaults
Assigning a server-side default in place of a client-omitted value during resource creation is an anti-pattern: it creates hybrid ownership where the field is client-owned but the server effectively chooses a value. State-driven clients and downstream tooling then cannot tell whether a returned value was client-set or server-filled, and they report drift on every reconciliation.
Optional request fields must be returned by the server with the same value (or absence of value) the client provided
components:
schemas:
Backup:
type: object
properties:
retentionDays:
type: integer
description:
Optional. Returned exactly as provided; absent when not set.Why:When the client omits
retentionDays, the server leaves it absent rather than filling in a default, so a read reflects exactly what the client set and no drift appears.components:
schemas:
Backup:
type: object
properties:
retentionDays:
type: integer
description: Optional. Defaults to 30 when omitted by the client.Why:The client omits
retentionDays, but the server returns30. The field is client-owned, yet the response carries a server-chosen value, so a declarative client sees a difference it never set and reconciles against it indefinitely.Identify optional request fields that are client-owned per Single Owner Fields.
Create a resource omitting each such field, then read it back.
Confirm the field is returned as the client left it — absent when omitted — rather than populated with a server-chosen default.
Report any optional client-owned field the server fills with a default on the response.
Legacy fields that cannot be remediated away from the optional-plus-server-default pattern must be annotated per IPA-131 so declarative tooling can reconcile the hybrid ownership
- New APIs must not rely on this escape hatch; it exists only for legacy fields that cannot change.
- Optional boolean fields are exempt and must not be annotated — they are governed by Boolean Values.
components:
schemas:
Backup:
type: object
properties:
retentionDays:
type: integer
x-xgen-server-computed-when-client-omitted: true
description: Legacy. Server assigns a value when the client omits it.Why:The legacy field keeps its server-default behavior but declares
x-xgen-server-computed-when-client-omitted, so tooling knows the returned value may be server-filled and does not treat it as client drift.components:
schemas:
Backup:
type: object
properties:
retentionDays:
type: integer
description: Legacy. Server assigns a value when the client omits it.Why:The field retains the optional-plus-server-default behavior but carries no annotation, so declarative tooling sees the server-assigned value as client-owned and reports spurious drift.
Identify optional client-owned fields for which the server still assigns a value when the client omits one.
Confirm each such field that cannot be remediated declares
x-xgen-server-computed-when-client-omitted: true.Confirm no boolean field carries the annotation.
Report any unannotated legacy field exhibiting the pattern, and any new (non-legacy) field relying on it.
Effective Values
There are instances where a service will allocate, generate, or calculate a value that may differ from what the client specified.
An attribute with an effective value must be expressed as two fields in the API:
- A client-owned mutable field that may be optionally set by the user and must not be modified by the service
- A server-owned read-only field that records the effective value decided on by the service
components:
schemas:
Cluster:
type: object
properties:
instanceSize:
type: string
effectiveInstanceSize:
type: string
readOnly: trueWhy:The desired
instanceSizeis a client-owned mutable field, whileeffectiveInstanceSizeis a server-owned read-only field recording the value the service decided on.Identify attributes whose effective value may differ from what the client specified.
Confirm each is expressed as two fields: a client-owned mutable field and a server-owned read-only field.
Flag attributes that collapse the desired and effective values into a single field.
Effective values must be named by prefixing
effectiveto the mutable field's namecomponents:
schemas:
Cluster:
type: object
properties:
instanceSize:
type: string
effectiveInstanceSize:
type: string
readOnly: trueWhy:The effective field is named
effectiveInstanceSize, prefixingeffectiveto the mutable field nameinstanceSize.In OpenAPI, the effective value field must have
readOnly: truecomponents:
schemas:
Cluster:
type: object
properties:
effectiveInstanceSize:
type: string
readOnly: trueWhy:The
effectiveInstanceSizefield setsreadOnly: true, marking the server-decided effective value as not settable by the client.
Example
A cluster's instance size may have a different computed value from the server if auto-scaling is enabled. The client specifies their desired instance size, but the server may scale it up or down based on load.
// For managing a cluster with auto-scaling enabled
{
"instanceSize": "M10",
"effectiveInstanceSize": "M30"
}
Sensitive Fields
Some resource fields carry secrets — passwords, API keys, private credentials. The API design must prevent accidental exposure of these values in responses.
Sensitive fields must be marked in OpenAPI so that consumers and tooling can identify them (see IPA-117 for the required annotations).
components:
schemas:
ApiKey:
type: object
properties:
displayName:
type: string
key:
type: string
format: password
writeOnly: trueWhy:The
keyproperty carriesformat: passwordandwriteOnly: true, so tooling and reviewers know the field contains a secret and must not appear in responses.components:
schemas:
ApiKey:
type: object
properties:
displayName:
type: string
key:
type: stringWhy:The
keyproperty lacks any annotation — noformat: password, nowriteOnly— so neither consumers nor tooling can tell that the field carries a secret.Identify fields that carry secrets — passwords, API keys, credentials, or other sensitive material.
For each sensitive field, confirm it carries an OpenAPI annotation (e.g.
format: password,writeOnly: true) that marks it as sensitive.Report sensitive fields that have no marking or rely only on naming conventions.
Depends on
Sensitive fields must follow one of the following information-flow patterns:
Write-only. The client submits the value on Create or Update and the server must not return it in any response.
components:
schemas:
ApiKeyCreate:
type: object
properties:
displayName:
type: string
key:
type: string
format: password
writeOnly: trueWhy:The
keyis write-only — the client sends it on create, and the server never returns it in subsequent Get or List responses.components:
schemas:
UserUpdate:
type: object
properties:
email:
type: string
password:
type: string
format: password
writeOnly: trueWhy:When updating a user, the client submits the new
passwordon Update. It iswriteOnly: true, so the server accepts it but never returns it in any response.components:
schemas:
ApiKey:
type: object
properties:
displayName:
type: string
key:
type: stringWhy:The
keyis not marked write-only, so the client expects it to appear in responses — leaking the secret.Identify fields whose value is submitted by the client and must not be returned.
Confirm each such field is marked
writeOnly: trueand does not appear in response schemas.Flag any write-only field that appears in a response schema.
Create-response-only. For values the client cannot retrieve later (e.g. server-generated API keys), the field may appear in the Create response so the client can capture it, but must not appear in subsequent Get or List responses. Create-response-only is the sanctioned exception to the schema consistency rule.
components:
schemas:
# Create (POST) response: returns the secret once
ApiKeyCreateResponse:
type: object
properties:
id:
type: string
displayName:
type: string
key:
type: string
format: password
readOnly: true
# Get and List response: omits the secret
ApiKey:
type: object
properties:
id:
type: string
displayName:
type: stringWhy:The server-generated
keyappears only in the Create response schema, markedreadOnlysince the client never sets it. The Get and List schema omits it entirely, so the secret cannot be retrieved later.components:
schemas:
# Single schema reused by Create, Get, and List
ApiKey:
type: object
properties:
id:
type: string
displayName:
type: string
key:
type: string
format: password
readOnly: trueWhy:A single response schema carries
keyacross Create, Get, and List, so the secret is returned on every read instead of only once on creation.Identify fields that are server-generated and shown only once (e.g., initial API key secret).
Confirm the field appears only in the Create response schema and is absent from the Get and List response schemas.
Flag any create-response-only field that appears in Get or List response schemas.
When a masked representation of a sensitive field (e.g. **** or a last-4
display value) needs to appear in Get or List responses,
it must be modeled as a separate read-only redacted sibling property
following the Effective Values pattern. The raw and
redacted values must not share the same property name. A redacted sibling
may be added to either a write-only or a create-response-only field
independently of the chosen information-flow pattern.
A masked display of a sensitive field (e.g.
****or a last-4 display value) must be modeled as a separate read-only redacted sibling property, not reusing the raw field's name.components:
schemas:
ApiKey:
type: object
properties:
key:
type: string
format: password
writeOnly: true
keyRedacted:
type: string
description: Last four characters of the API key.
readOnly: trueWhy:The raw value is
key(write-only); the masked display iskeyRedacted(read-only, last-4 display). They are distinct properties with different names, so no ambiguity exists about which carries the secret.components:
schemas:
ApiKey:
type: object
properties:
key:
type: stringWhy:A single property named
keycannot simultaneously be write-only and carry a masked display. Consumers cannot tell whether the value is the raw secret or a redacted version.Find fields whose response value is a masked or truncated display of a secret (e.g.
****, last-4).Confirm the masked value lives on a separate property with a distinct name (e.g.
keyRedacted) rather than replacing the raw field.Report any field that mixes a raw secret and a masked display under the same property name.
Boolean Values
Optional boolean fields must default to
false- Many serialization systems won’t distinguish
falsevalues from unset values which introduces complications when a boolean defaults totrue - This avoids a tri-state (true / false / unset) that is hard for both producers and consumers to reason about, so optional booleans must not carry the server-default annotation used for other optional fields
components:
schemas:
ClusterDescriptionProcessArgs:
type: object
properties:
javascriptDisabled:
type: boolean
default: falseWhy:The boolean is modeled as
javascriptDisableddefaulting tofalse, so an unset value and afalsevalue mean the same thing and no user intervention is required for the default.components:
schemas:
ClusterDescriptionProcessArgs:
type: object
properties:
javascriptEnabled:
type: boolean
default: trueWhy:javascriptEnableddefaults totrue, but many serialization systems cannot distinguishfalsefrom unset, forcing users to always set the value explicitly.Identify the default value of each boolean field.
Confirm the default is
false, renaming the field if necessary so thefalsedefault carries the intended meaning.- Flag boolean fields that default to
true.
- Many serialization systems won’t distinguish
Example
package example
// Bad
// Given the go struct
type ClusterDescriptionProcessArgs struct {
JavascriptEnabled bool `json:"javascriptEnabled"`
}
// the following call will default to false and requires user intervention to set
// JavascriptEnabled to true
sdk.UpdateClusterAdvancedConfiguration(new(ClusterDescriptionProcessArgs))
// Users need to always set the true value even if the API defaults to true
sdk.UpdateClusterAdvancedConfiguration(&ClusterDescriptionProcessArgs{JavascriptEnabled: true}))
// Good
type ClusterDescriptionProcessArgs struct {
JavascriptDisabled bool `json:"javascriptEnabled"`
}
// the following call will default to false and requires user intervention to set
// JavascriptDisabled to true
sdk.UpdateClusterAdvancedConfiguration(new(ClusterDescriptionProcessArgs))