IPA-113: Singleton Resources
An API may define singleton resources. A singleton resource must always exist by virtue of the existence of its parent, with one and exactly one per parent.
Guidance
Singleton resources must not have a user-provided or system-generated ID
paths:
/groups/{groupId}/settings:
get:
operationId: getGroupSettingsWhy:The singleton
settingsis addressed entirely through its parentgroupId. There is exactly one per group, so no separate identifier is needed or exposed.paths:
/groups/{groupId}/settings/{settingsId}:
get:
operationId: getGroupSettingsWhy:A
settingsIdpath parameter implies multiplesettingsper group and a way to enumerate them. A singleton has exactly one instance per parent, so an identifier has nothing to distinguish.Singleton resources must not define the Create or Delete standard methods
- The singleton is implicitly created or deleted when its parent is created or deleted
paths:
/groups/{groupId}/settings:
get:
operationId: getGroupSettings
patch:
operationId: updateGroupSettingsWhy:The singleton exposes only Get and Update. Its lifecycle is tied to the parent
group: thesettingscome into existence when the group is created and are removed when the group is deleted, so explicit Create and Delete methods would be meaningless.paths:
/groups/{groupId}/settings:
post:
operationId: createGroupSettings
delete:
operationId: deleteGroupSettingsWhy:Create and Delete imply the singleton can be brought into or out of existence independently of its parent. A singleton must always exist by virtue of its parent, so there is no valid state in which it can be created or deleted on its own.
Singleton resources must define the Get method
paths:
/groups/{groupId}/settings:
get:
operationId: getGroupSettings
responses:
"200":
description: The group settings.Why:The singleton defines Get, so consumers can always read the current state of the resource that the parent guarantees exists.
Singleton resources should define the Update method, unless the resource is read-only
paths:
/groups/{groupId}/settings:
get:
operationId: getGroupSettings
patch:
operationId: updateGroupSettingsWhy:The mutable singleton exposes Update alongside Get, so consumers can change the settings without recreating the parent. Read-only singletons are the exception and intentionally omit Update.
Identify each singleton resource: a resource addressed through its parent with no identifier of its own.
Determine whether the singleton is intended to be read-only — modifiable only by the server, never by API consumers.
For a singleton that is not read-only, confirm it defines an Update method. Flag any mutable singleton that omits Update.
Singleton resources may define custom methods as appropriate
Example
GET /groups/${groupId}/settings
###
PATCH /groups/${groupId}/settings
Read-Only Singleton Resources
Read-only singleton resources are singleton resources that cannot be modified by API consumers.
Read-only singleton resources must have only the Get method
paths:
/groups/{groupId}/billingStatus:
get:
operationId: getGroupBillingStatus
responses:
"200":
description: The group billing status.Why:The read-only singleton
billingStatusexposes only Get. It is computed and owned by the server, so consumers can read it but never mutate it.Read-only singleton resources must not have Create, Update, or Delete methods
paths:
/groups/{groupId}/billingStatus:
get:
operationId: getGroupBillingStatusWhy:Only Get is present. Create and Delete are already disallowed for all singletons, and a read-only singleton additionally omits Update, so no method can mutate it.
paths:
/groups/{groupId}/billingStatus:
get:
operationId: getGroupBillingStatus
patch:
operationId: updateGroupBillingStatusWhy:The
patchis an Update method on a read-only singleton. By definition a read-only singleton cannot be modified by consumers, so exposing Update contradicts the resource's contract.Read-only singleton resources may have custom methods as appropriate, provided they do not modify the resource
All response schema properties for read-only singleton resources must be marked as read-only
- In OpenAPI, this means all properties must have
readOnly: true - All fields in read-only singleton resources are server-owned. For guidance on server-owned fields, see IPA-111
components:
schemas:
GroupBillingStatus:
type: object
properties:
state:
type: string
readOnly: true
lastChargedDate:
type: string
format: date-time
readOnly: trueWhy:Every property carries
readOnly: true, signalling that all fields are server-owned and cannot be set by consumers. This matches the read-only contract of the singleton.components:
schemas:
GroupBillingStatus:
type: object
properties:
state:
type: string
readOnly: true
lastChargedDate:
type: string
format: date-timeWhy:lastChargedDateis missingreadOnly: true, so generated clients treat it as writable. On a read-only singleton no field is consumer-writable, so leaving any property unmarked misrepresents the contract.Identify the response schema for each read-only singleton resource.
Enumerate every property in that schema, including nested object properties.
Flag any property that does not have
readOnly: true.
- In OpenAPI, this means all properties must have
Unsupported operations on read-only singleton resources should return
405 Not Allowedpaths:
/groups/{groupId}/billingStatus:
get:
operationId: getGroupBillingStatus
responses:
"200":
description: The group billing status.Why:The read-only singleton documents only
get. The unsupportedpatchis kept out of the spec, and aPATCH /groups/{groupId}/billingStatuscall resolves to405 Not Allowedat the server — the method is recognized but not permitted, rather than404(resource missing) or403(forbidden). The behavior lives in the implementation while the contract stays clean.paths:
/groups/{groupId}/billingStatus:
get:
operationId: getGroupBillingStatus
patch:
operationId: updateGroupBillingStatus
responses:
"404":
description: Billing status not found.Why:Documenting
patchand returning404muddles two different things: the resource doesn't exist, versus the method isn't allowed. The singleton always exists by virtue of its parent, and the method is recognized but disallowed, so the honest response is405— and the method should not be documented at all.Identify each read-only singleton resource: only Get is documented, with no Create, Update, or Delete.
In the implementation behind that path (routing or controller code), check what an unsupported mutation —
POST,PUT,PATCH, orDELETE— returns.Flag any unsupported mutation that resolves to anything other than
405 Not Allowed(for example404,403, or a success code).
Unsupported operations must not be documented
paths:
/groups/{groupId}/billingStatus:
get:
operationId: getGroupBillingStatus
responses:
"200":
description: The group billing status.Why:The read-only singleton lists only the operation it actually serves:
getGroupBillingStatus. There are no mutation operations in the spec, so doc generators and SDK builders produce only methods the API honors. No generated call targets an endpoint the server will reject.paths:
/groups/{groupId}/billingStatus:
get:
operationId: getGroupBillingStatus
delete:
operationId: deleteGroupBillingStatus
responses:
"405":
description: Method not allowed.Why:Even though the
deleteadvertises405, documenting it surfaces a method the API does not support. Doc generators and SDK builders will emit adeleteGroupBillingStatuscall that can never succeed.
Resetting Singleton Resources
Singleton resources can be restored to their default state without deleting the
parent resource. For such cases, a :reset custom method must
be used.
The
:resetcustom method name must be reserved exclusively for restoring singleton resources to their default statepaths:
/groups/{groupId}/settings:reset:
post:
operationId: resetGroupSettings
responses:
"200":
description: The group settings restored to default.Why:The
:resetmethod does exactly what its reserved name implies: it restores the singletonsettingsto their default state. The name is not reused for any other behavior.paths:
/groups/{groupId}/settings:reset:
post:
operationId: resetGroupSettings
description: Recalculates derived metrics for the group settings.Why:Here
:resetis used to recalculate metrics rather than to restore default state. Overloading the reserved name with unrelated behavior makes the method's contract ambiguous across the API.Locate every custom method named
:resetacross the spec.Confirm each one restores a singleton resource to its default state, as described in its summary, description, and behavior.
Flag any
:resetmethod that performs something other than restoring default state.
The
:resetcustom method must use thePOSTHTTP methodpaths:
/groups/{groupId}/settings:reset:
post:
operationId: resetGroupSettingsWhy:The
:resetcustom method is defined underpost, matching the required HTTP method for reset.paths:
/groups/{groupId}/settings:reset:
put:
operationId: resetGroupSettingsWhy:The
:resetmethod is defined underputrather thanpost. Reset custom methods must usePOST.The
:resetcustom method must be idempotent- Calling reset multiple times must produce the same result as calling it once
paths:
/groups/{groupId}/settings:reset:
post:
operationId: resetGroupSettings
responses:
"200":
description: The group settings restored to their default state.Why:Reset restores the singleton to a fixed default state. Calling it once or many times leaves the resource in the same default state, so the operation is idempotent.
Identify each
:resetcustom method on a singleton resource.In the implementation, confirm that reset sets the resource to a fixed default state rather than applying a relative or incremental change.
Verify that invoking reset repeatedly leaves the resource in the same state as a single invocation. Flag any reset whose repeated calls produce differing results.
The
:resetcustom method must return a 200 OK response with the reset resource in the response bodypaths:
/groups/{groupId}/settings:reset:
post:
operationId: resetGroupSettings
responses:
"200":
description: The group settings restored to their default state.
content:
application/json:
schema:
$ref: "#/components/schemas/GroupSettings"Why:Reset responds with
200 OKand returns the resetGroupSettingsin the response body, so consumers immediately see the restored default state.paths:
/groups/{groupId}/settings:reset:
post:
operationId: resetGroupSettings
responses:
"204":
description: Settings reset.Why:A
204 No Contentreturns no body, so consumers cannot see the restored state without a follow-up Get. Reset must return200 OKwith the reset resource in the body.The
:resetcustom method must not have a request bodypaths:
/groups/{groupId}/settings:reset:
post:
operationId: resetGroupSettings
responses:
"200":
description: The group settings restored to their default state.Why:Reset takes no input — it always restores the same default state — so the operation defines no
requestBody.paths:
/groups/{groupId}/settings:reset:
post:
operationId: resetGroupSettings
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/GroupSettings"
responses:
"200":
description: The group settings restored to their default state.Why:A
requestBodyimplies reset accepts caller-supplied values. Reset restores a fixed default state and must take no input, so it must not define a request body.Read-only singleton resources must not define a
:resetcustom method- Read-only singleton resources cannot be modified, so reset is not applicable
paths:
/groups/{groupId}/billingStatus:
get:
operationId: getGroupBillingStatusWhy:The read-only singleton exposes only Get and defines no
:resetmethod. Since the resource cannot be modified, there is no default state to restore.paths:
/groups/{groupId}/billingStatus:
get:
operationId: getGroupBillingStatus
/groups/{groupId}/billingStatus:reset:
post:
operationId: resetGroupBillingStatusWhy:Defining
:reseton a read-only singleton contradicts its contract: reset modifies the resource by restoring default state, but a read-only singleton cannot be modified by consumers at all.