{
  "openapi": "3.1.1",
  "info": {
    "title": "OmniLife Simple API",
    "description": "Australian life-insurance new-business pricing, as a small JSON API: quote premiums, project\nthem over time, and compare product features across insurers, for one insured person at a\ntime. It is built to be driven by an AI agent as readily as by application code - every\nvalue is a readable name rather than an insurer code, and every failure carries a stable\n`code` to branch on.\n\n### A client is the insured person\n\nThroughout this API, a `client` is the person being insured - their age or date of birth,\ngender, occupation, state, smoker status and income. It is never the business calling the\nAPI; that is an API user, identified by the credentials used at the token endpoint. Each\nclient belongs to exactly one API user and is invisible to every other.\n\n### The call order\n\n1. `POST /api/auth/token` - exchange client credentials for a bearer token.\n2. `GET /api/v1/occupations` and `GET /api/v1/suppliers` - resolve the occupation id and the\n   supplier and product codes a scenario refers to. Both are reference data, specific to the\n   calling API user's entitlements.\n3. `POST /api/v1/clients` - create the insured person once; the response carries the\n   `clientId` every later call uses.\n4. `POST /api/v1/clients/{clientId}/quotes`, `/projections` or `/research` - price a\n   scenario, project it forward, or compare feature support. All three take the same\n   scenario body, none of them stores anything, and each can be re-sent with a changed\n   scenario as often as needed.\n5. `GET /api/v1/billing/periods` - check what has been charged, and\n   `GET /api/v1/clients` for each client's `exemptUntil`.\n\n### Authentication\n\nEvery endpoint except the token endpoint and the two `/.well-known/` discovery documents\nrequires an `Authorization: Bearer` header. Tokens are issued by the OAuth2\nclient-credentials grant, last an hour, and cannot be refreshed - request a new one. Cache\nthe token and reuse it until it is close to expiry: the token endpoint is rate limited far\nmore tightly than the rest of the API, so re-authenticating per request will start failing.\n\n### Rate limits\n\n100 requests per minute across the API, and a much tighter 10 per minute at the token\nendpoint. Exceeding either returns `429` with a `Retry-After` header giving the seconds to\nwait.\n\n### Errors\n\nEvery failure this API raises returns an RFC 9457 problem document carrying a stable `code`\n(see the `ProblemCode` schema) - branch on that rather than on the status code, which is\nshared by several conditions. A `502` means pricing could not be completed and the request\nwas not charged; it can be retried. Every response, successful or not, carries an\n`X-Correlation-ID` header; quote it when contacting support.\n\n### Version policy\n\n`v1` changes additively. New fields may appear in any response and new members in any\nstring-valued enum, so a consumer must ignore what it does not recognise rather than fail on\nit. Anything that cannot be done additively will appear under `/api/v2`; a field being\nretired is marked `deprecated` here first.",
    "contact": {
      "name": "OmniLife Simple support",
      "url": "https://www.omnium.com.au",
      "email": "support@omnium.com.au"
    },
    "version": "1.0.0"
  },
  "servers": [
    {
      "url": "https://simple.omnilife.com.au"
    }
  ],
  "paths": {
    "/.well-known/oauth-authorization-server": {
      "get": {
        "tags": [
          "Auth"
        ],
        "summary": "Discover the token endpoint and supported grant",
        "description": "Returns this API's RFC 8414 authorization server metadata: the issuer, the absolute URL of the token endpoint, and the single grant type and client authentication method supported. A client that reads this document needs nothing else to obtain a token. Served anonymously and not rate limited. The API is its own authorization server, so the issuer here is also the resource identifier returned by `/.well-known/oauth-protected-resource`.",
        "operationId": "OAuthAuthorizationServerMetadata",
        "responses": {
          "200": {
            "description": "Returns the authorization server metadata document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthAuthorizationServerMetadata"
                },
                "example": {
                  "issuer": "https://simple.omnilife.com.au",
                  "token_endpoint": "https://simple.omnilife.com.au/api/auth/token",
                  "grant_types_supported": [
                    "client_credentials"
                  ],
                  "token_endpoint_auth_methods_supported": [
                    "client_secret_post"
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too many requests - the rate limit has been exceeded. Wait for the number of seconds in the Retry-After header before retrying.",
            "headers": {
              "Retry-After": {
                "description": "The number of seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "405": {
            "description": "This path does not accept the method the request used; it accepts `GET`. The problem's `code` is `method_not_allowed`. The `Allow` header lists the accepted methods.",
            "headers": {
              "Allow": {
                "description": "The methods this path accepts, comma-separated.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [ ]
      }
    },
    "/.well-known/oauth-protected-resource": {
      "get": {
        "tags": [
          "Auth"
        ],
        "summary": "Discover this API's resource identifier and authorization server",
        "description": "Returns this API's RFC 9728 protected resource metadata: the canonical resource identifier, and the authorization servers that issue tokens for it. A 401 from any endpoint carries a `WWW-Authenticate` header whose `resource_metadata` names this document, so a client that meets an unexpected 401 can walk from here to `/.well-known/oauth-authorization-server` and on to the token endpoint without prior configuration. Served anonymously and not rate limited.",
        "operationId": "OAuthProtectedResourceMetadata",
        "responses": {
          "200": {
            "description": "Returns the protected resource metadata document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthProtectedResourceMetadata"
                },
                "example": {
                  "resource": "https://simple.omnilife.com.au",
                  "authorization_servers": [
                    "https://simple.omnilife.com.au"
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too many requests - the rate limit has been exceeded. Wait for the number of seconds in the Retry-After header before retrying.",
            "headers": {
              "Retry-After": {
                "description": "The number of seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "405": {
            "description": "This path does not accept the method the request used; it accepts `GET`. The problem's `code` is `method_not_allowed`. The `Allow` header lists the accepted methods.",
            "headers": {
              "Allow": {
                "description": "The methods this path accepts, comma-separated.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        },
        "security": [ ]
      }
    },
    "/api/v1/suppliers": {
      "get": {
        "tags": [
          "Suppliers"
        ],
        "summary": "List the suppliers available to the calling API user",
        "description": "Returns the suppliers visible to the calling API user, each bundled with its products, current PDS/TMD documents, and SVG logo. Reference data is per API user and is not cached. Requires a bearer token.",
        "operationId": "ListSuppliers",
        "responses": {
          "200": {
            "description": "Returns the suppliers visible to the caller, each bundled with its products, current documents, and logo.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/SupplierResponse"
                  }
                }
              }
            }
          },
          "422": {
            "description": "The pricing engine refused this request (`request_not_priceable`). Retrying unchanged fails the same way; quote the correlation id to support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "502": {
            "description": "The pricing engine could not be reached (`omnilife_unavailable` - retry the request) or answered with something this API could not interpret (`omnilife_response_invalid` - retrying is unlikely to help).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "No access token was supplied, or the token is expired or invalid. The response has no body. Request a new token from the token endpoint and retry.",
            "headers": {
              "WWW-Authenticate": {
                "description": "A `Bearer` challenge whose `resource_metadata` names the URL of this API's protected-resource metadata document.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests - the rate limit has been exceeded. Wait for the number of seconds in the Retry-After header before retrying.",
            "headers": {
              "Retry-After": {
                "description": "The number of seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "405": {
            "description": "This path does not accept the method the request used; it accepts `GET`. The problem's `code` is `method_not_allowed`. The `Allow` header lists the accepted methods.",
            "headers": {
              "Allow": {
                "description": "The methods this path accepts, comma-separated.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/suppliers/{supplierCode}/products": {
      "get": {
        "tags": [
          "Suppliers"
        ],
        "summary": "List the products offered by one supplier",
        "description": "Returns the products for the given supplier code, in the same shape as the products bundled in GET /suppliers. Requires a bearer token.",
        "operationId": "ListSupplierProducts",
        "parameters": [
          {
            "name": "supplierCode",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Returns the products offered by the named supplier.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/SupplierProduct"
                  }
                }
              }
            }
          },
          "404": {
            "description": "No supplier exists with the given `supplierCode`.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "422": {
            "description": "The pricing engine refused this request (`request_not_priceable`). Retrying unchanged fails the same way; quote the correlation id to support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "502": {
            "description": "The pricing engine could not be reached (`omnilife_unavailable` - retry the request) or answered with something this API could not interpret (`omnilife_response_invalid` - retrying is unlikely to help).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "No access token was supplied, or the token is expired or invalid. The response has no body. Request a new token from the token endpoint and retry.",
            "headers": {
              "WWW-Authenticate": {
                "description": "A `Bearer` challenge whose `resource_metadata` names the URL of this API's protected-resource metadata document.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests - the rate limit has been exceeded. Wait for the number of seconds in the Retry-After header before retrying.",
            "headers": {
              "Retry-After": {
                "description": "The number of seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "405": {
            "description": "This path does not accept the method the request used; it accepts `GET`. The problem's `code` is `method_not_allowed`. The `Allow` header lists the accepted methods.",
            "headers": {
              "Allow": {
                "description": "The methods this path accepts, comma-separated.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/occupations": {
      "get": {
        "tags": [
          "Occupations"
        ],
        "summary": "Search the occupation catalog",
        "description": "Matches occupation titles and their aliases against the search text. Requires a bearer token.",
        "operationId": "SearchOccupations",
        "parameters": [
          {
            "name": "Search",
            "in": "query",
            "description": "Free-text search matched against occupation titles and their aliases.",
            "required": true,
            "schema": {
              "maxLength": 200,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Returns the occupations matching the search text.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/OccupationResponse"
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request failed validation; the response body lists the invalid fields.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          },
          "422": {
            "description": "The pricing engine refused this request (`request_not_priceable`). Retrying unchanged fails the same way; quote the correlation id to support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "502": {
            "description": "The pricing engine could not be reached (`omnilife_unavailable` - retry the request) or answered with something this API could not interpret (`omnilife_response_invalid` - retrying is unlikely to help).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "No access token was supplied, or the token is expired or invalid. The response has no body. Request a new token from the token endpoint and retry.",
            "headers": {
              "WWW-Authenticate": {
                "description": "A `Bearer` challenge whose `resource_metadata` names the URL of this API's protected-resource metadata document.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests - the rate limit has been exceeded. Wait for the number of seconds in the Retry-After header before retrying.",
            "headers": {
              "Retry-After": {
                "description": "The number of seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "405": {
            "description": "This path does not accept the method the request used; it accepts `GET`. The problem's `code` is `method_not_allowed`. The `Allow` header lists the accepted methods.",
            "headers": {
              "Allow": {
                "description": "The methods this path accepts, comma-separated.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/occupations/categories": {
      "get": {
        "tags": [
          "Occupations"
        ],
        "summary": "List the curated generic occupation categories",
        "description": "A curated short list of generic occupation categories (Homemaker, Generic 1-9, Unemployed) with the `Generic N:` prefix stripped from the description. Each entry is a real occupation id - a coarse choice for general-advice journeys. Requires a bearer token.",
        "operationId": "ListOccupationCategories",
        "responses": {
          "200": {
            "description": "Returns the curated list of generic occupation categories.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/OccupationResponse"
                  }
                }
              }
            }
          },
          "422": {
            "description": "The pricing engine refused this request (`request_not_priceable`). Retrying unchanged fails the same way; quote the correlation id to support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "502": {
            "description": "The pricing engine could not be reached (`omnilife_unavailable` - retry the request) or answered with something this API could not interpret (`omnilife_response_invalid` - retrying is unlikely to help).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "No access token was supplied, or the token is expired or invalid. The response has no body. Request a new token from the token endpoint and retry.",
            "headers": {
              "WWW-Authenticate": {
                "description": "A `Bearer` challenge whose `resource_metadata` names the URL of this API's protected-resource metadata document.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests - the rate limit has been exceeded. Wait for the number of seconds in the Retry-After header before retrying.",
            "headers": {
              "Retry-After": {
                "description": "The number of seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "405": {
            "description": "This path does not accept the method the request used; it accepts `GET`. The problem's `code` is `method_not_allowed`. The `Allow` header lists the accepted methods.",
            "headers": {
              "Allow": {
                "description": "The methods this path accepts, comma-separated.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/suppliers/{supplierCode}/occupations": {
      "get": {
        "tags": [
          "Occupations"
        ],
        "summary": "List one supplier's own occupation list",
        "description": "Used to populate a client's `supplierOccupationOverrides` for the given supplier. Requires a bearer token.",
        "operationId": "ListSupplierOccupations",
        "parameters": [
          {
            "name": "supplierCode",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Returns the named supplier's own occupation list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/OccupationResponse"
                  }
                }
              }
            }
          },
          "404": {
            "description": "No supplier exists with the given `supplierCode`.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "422": {
            "description": "The pricing engine refused this request (`request_not_priceable`). Retrying unchanged fails the same way; quote the correlation id to support.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "502": {
            "description": "The pricing engine could not be reached (`omnilife_unavailable` - retry the request) or answered with something this API could not interpret (`omnilife_response_invalid` - retrying is unlikely to help).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "No access token was supplied, or the token is expired or invalid. The response has no body. Request a new token from the token endpoint and retry.",
            "headers": {
              "WWW-Authenticate": {
                "description": "A `Bearer` challenge whose `resource_metadata` names the URL of this API's protected-resource metadata document.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests - the rate limit has been exceeded. Wait for the number of seconds in the Retry-After header before retrying.",
            "headers": {
              "Retry-After": {
                "description": "The number of seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "405": {
            "description": "This path does not accept the method the request used; it accepts `GET`. The problem's `code` is `method_not_allowed`. The `Allow` header lists the accepted methods.",
            "headers": {
              "Allow": {
                "description": "The methods this path accepts, comma-separated.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/clients": {
      "post": {
        "tags": [
          "Clients"
        ],
        "summary": "Create a client",
        "description": "Persists a new Client (the life-insured person being quoted) owned by the calling API user. Requires a bearer token.",
        "operationId": "CreateClient",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateClientRequest"
              },
              "example": {
                "clientDetails": {
                  "dateOfBirth": "1990-06-15",
                  "gender": "Male",
                  "australianState": "NSW",
                  "smokerStatus": "NonSmoker",
                  "income": 120000,
                  "occupationId": "231111",
                  "employmentStatus": "Employee"
                },
                "retention": {
                  "timeToLiveDays": 1095,
                  "isSlidingExpiration": true
                },
                "externalReference": "crm-84213"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Returns the newly created client, including its generated `clientId`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClientResponse"
                },
                "example": {
                  "clientId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                  "clientDetails": {
                    "dateOfBirth": "1990-06-15",
                    "gender": "Male",
                    "australianState": "NSW",
                    "smokerStatus": "NonSmoker",
                    "income": 120000,
                    "occupationId": "231111",
                    "employmentStatus": "Employee"
                  },
                  "retention": {
                    "timeToLiveDays": 1095,
                    "isSlidingExpiration": true
                  },
                  "externalReference": "crm-84213",
                  "createdAt": "2026-03-10T02:14:00Z",
                  "updatedAt": "2026-03-10T02:14:00Z"
                }
              }
            }
          },
          "400": {
            "description": "The request failed validation; the response body lists the invalid fields.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "No access token was supplied, or the token is expired or invalid. The response has no body. Request a new token from the token endpoint and retry.",
            "headers": {
              "WWW-Authenticate": {
                "description": "A `Bearer` challenge whose `resource_metadata` names the URL of this API's protected-resource metadata document.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests - the rate limit has been exceeded. Wait for the number of seconds in the Retry-After header before retrying.",
            "headers": {
              "Retry-After": {
                "description": "The number of seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "415": {
            "description": "The body was sent with a content type this operation doesn't accept. Resend it as `application/json`; the problem's `detail` names the accepted types too.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "405": {
            "description": "This path does not accept the method the request used; it accepts `POST`, `GET`. The problem's `code` is `method_not_allowed`. The `Allow` header lists the accepted methods.",
            "headers": {
              "Allow": {
                "description": "The methods this path accepts, comma-separated.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Clients"
        ],
        "summary": "List the caller's clients",
        "description": "Returns a page of the calling API user's clients, excluding soft-deleted ones, ordered oldest-first. Cursor-based pagination: pass the previous response's `nextCursor` as the `cursor` query parameter to fetch the next page. `pageSize` defaults to 50 and is capped at 200. Optional filters (combined with AND): `createdAfter`/`createdBefore` (ISO-8601, on creation time), `updatedAfter` (on last data change - use for delta sync), and `externalReference` (exact match). Pass the same filters on every page request; the cursor encodes only the sort position. Requires a bearer token.",
        "operationId": "ListClients",
        "parameters": [
          {
            "name": "pageSize",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "createdAfter",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "createdBefore",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "updatedAfter",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "externalReference",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Returns a page of the caller's clients.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PagedResultOfClientResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request failed validation; the response body lists the invalid fields.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "No access token was supplied, or the token is expired or invalid. The response has no body. Request a new token from the token endpoint and retry.",
            "headers": {
              "WWW-Authenticate": {
                "description": "A `Bearer` challenge whose `resource_metadata` names the URL of this API's protected-resource metadata document.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests - the rate limit has been exceeded. Wait for the number of seconds in the Retry-After header before retrying.",
            "headers": {
              "Retry-After": {
                "description": "The number of seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "405": {
            "description": "This path does not accept the method the request used; it accepts `POST`, `GET`. The problem's `code` is `method_not_allowed`. The `Allow` header lists the accepted methods.",
            "headers": {
              "Allow": {
                "description": "The methods this path accepts, comma-separated.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/clients/{clientId}": {
      "get": {
        "tags": [
          "Clients"
        ],
        "summary": "Get a client's details",
        "description": "Returns 404 both when no such client exists and when it belongs to another API user - the two cases are indistinguishable to the caller. Requires a bearer token.",
        "operationId": "GetClient",
        "parameters": [
          {
            "name": "clientId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Returns the client's details.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClientResponse"
                }
              }
            }
          },
          "400": {
            "description": "The `clientId` path segment is not a UUID; the response body names it.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No client exists with the given `clientId`, or it belongs to another API user - the two cases are indistinguishable to the caller.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "No access token was supplied, or the token is expired or invalid. The response has no body. Request a new token from the token endpoint and retry.",
            "headers": {
              "WWW-Authenticate": {
                "description": "A `Bearer` challenge whose `resource_metadata` names the URL of this API's protected-resource metadata document.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests - the rate limit has been exceeded. Wait for the number of seconds in the Retry-After header before retrying.",
            "headers": {
              "Retry-After": {
                "description": "The number of seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "405": {
            "description": "This path does not accept the method the request used; it accepts `GET`, `PATCH`, `DELETE`. The problem's `code` is `method_not_allowed`. The `Allow` header lists the accepted methods.",
            "headers": {
              "Allow": {
                "description": "The methods this path accepts, comma-separated.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Clients"
        ],
        "summary": "Partially update a client",
        "description": "Applies a JSON Merge Patch (RFC 7396) to the client, re-runs the same validation as create, and persists the result. The body is a partial client object: a present field is overwritten, an explicit null clears an optional field, and an omitted field is left unchanged. Send the body with content type `application/merge-patch+json` (or `application/json`). Immutable fields (clientId, audit fields, isDeleted, the owning API user) aren't part of the patchable shape, so including them is rejected with 400 rather than silently ignored. Returns 404 both when no such client exists and when it belongs to another API user. The identity fields (dateOfBirth/age, gender) lock 30 minutes after creation: after that window a change is only accepted when it's consistent with the same person - an exact DOB→age shed, an age→DOB upgrade consistent with the original assertion, or an in-band re-assertion of an age-only client's current estimated age. An inconsistent change returns 409 with an `identity_locked` problem code and the offending field name(s); the remedy is to create a new client. Requires a bearer token.",
        "operationId": "PatchClient",
        "parameters": [
          {
            "name": "clientId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "description": "The fields to change, as an RFC 7396 JSON Merge Patch. A present field is overwritten, an explicit null clears a clearable field, and an omitted field is left unchanged. Required: an absent body is rejected with 400.",
          "content": {
            "application/merge-patch+json": {
              "schema": {
                "type": "object",
                "properties": {
                  "clientDetails": {
                    "type": "object",
                    "properties": {
                      "dateOfBirth": {
                        "type": [
                          "null",
                          "string"
                        ],
                        "description": "Date of birth. Supplying this clears `age` - a client is dated or aged, never both.",
                        "format": "date"
                      },
                      "age": {
                        "type": [
                          "null",
                          "integer"
                        ],
                        "description": "Age in years, as at today. Supplying this clears `dateOfBirth`.",
                        "format": "int32"
                      },
                      "gender": {
                        "enum": [
                          "Male",
                          "Female",
                          null
                        ],
                        "type": [
                          "null",
                          "string"
                        ],
                        "description": "The life-insured's gender. Cannot be cleared."
                      },
                      "australianState": {
                        "enum": [
                          "ACT",
                          "NSW",
                          "NT",
                          "QLD",
                          "SA",
                          "TAS",
                          "VIC",
                          "WA",
                          null
                        ],
                        "type": [
                          "null",
                          "string"
                        ],
                        "description": "The state of residence. Cannot be cleared."
                      },
                      "smokerStatus": {
                        "enum": [
                          "Smoker",
                          "NonSmoker",
                          null
                        ],
                        "type": [
                          "null",
                          "string"
                        ],
                        "description": "Smoker status. Cannot be cleared."
                      },
                      "income": {
                        "type": [
                          "null",
                          "integer"
                        ],
                        "description": "Annual income excluding super, in dollars. Cannot be cleared; use 0 for \"unknown\".",
                        "format": "int32"
                      },
                      "occupationId": {
                        "type": [
                          "null",
                          "string"
                        ],
                        "description": "An OmniLife occupation id, from GET /occupations or /occupations/categories. Cannot be cleared."
                      },
                      "employmentStatus": {
                        "enum": [
                          "Employee",
                          "SelfEmployed",
                          "Homemaker",
                          "Unemployed",
                          null
                        ],
                        "type": [
                          "null",
                          "string"
                        ],
                        "description": "Employment status. Cannot be cleared."
                      },
                      "supplierOccupationOverrides": {
                        "type": [
                          "null",
                          "object"
                        ],
                        "additionalProperties": {
                          "type": "string"
                        },
                        "description": "Maps a supplier code to that supplier's own occupation code. Merged entry by entry: an entry\nsupplied is added or replaced, an entry set to null is removed, and entries not mentioned\nsurvive. Null for the whole field clears every override."
                      }
                    },
                    "description": "The changeable subset of ClientDetails. The identity fields (`dateOfBirth`, `age`, `gender`)\nlock shortly after the client is created; past that window a change is accepted only when it is\nconsistent with the same person, and rejected with 409 otherwise."
                  },
                  "retention": {
                    "type": "object",
                    "properties": {
                      "timeToLiveDays": {
                        "type": [
                          "null",
                          "integer"
                        ],
                        "description": "Days until the client's PII is purged. Null means never expire.",
                        "format": "int32"
                      },
                      "isSlidingExpiration": {
                        "type": [
                          "null",
                          "boolean"
                        ],
                        "description": "When true, billable activity resets the retention clock. Cannot be cleared."
                      }
                    },
                    "description": "The changeable subset of Retention."
                  },
                  "externalReference": {
                    "type": [
                      "null",
                      "string"
                    ],
                    "description": "The caller's own correlation key. Null clears it."
                  }
                },
                "description": "A partial update to a client, as an RFC 7396 JSON Merge Patch. Omit a field to leave it\nunchanged; pass null to clear it, where the field is clearable - `externalReference`,\n`retention.timeToLiveDays`, or a single `supplierOccupationOverrides` entry. Clearing a field\nthat has no empty state is rejected rather than silently ignored."
              },
              "example": {
                "clientDetails": {
                  "income": 135000
                },
                "externalReference": null
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "clientDetails": {
                    "type": "object",
                    "properties": {
                      "dateOfBirth": {
                        "type": [
                          "null",
                          "string"
                        ],
                        "description": "Date of birth. Supplying this clears `age` - a client is dated or aged, never both.",
                        "format": "date"
                      },
                      "age": {
                        "type": [
                          "null",
                          "integer"
                        ],
                        "description": "Age in years, as at today. Supplying this clears `dateOfBirth`.",
                        "format": "int32"
                      },
                      "gender": {
                        "enum": [
                          "Male",
                          "Female",
                          null
                        ],
                        "type": [
                          "null",
                          "string"
                        ],
                        "description": "The life-insured's gender. Cannot be cleared."
                      },
                      "australianState": {
                        "enum": [
                          "ACT",
                          "NSW",
                          "NT",
                          "QLD",
                          "SA",
                          "TAS",
                          "VIC",
                          "WA",
                          null
                        ],
                        "type": [
                          "null",
                          "string"
                        ],
                        "description": "The state of residence. Cannot be cleared."
                      },
                      "smokerStatus": {
                        "enum": [
                          "Smoker",
                          "NonSmoker",
                          null
                        ],
                        "type": [
                          "null",
                          "string"
                        ],
                        "description": "Smoker status. Cannot be cleared."
                      },
                      "income": {
                        "type": [
                          "null",
                          "integer"
                        ],
                        "description": "Annual income excluding super, in dollars. Cannot be cleared; use 0 for \"unknown\".",
                        "format": "int32"
                      },
                      "occupationId": {
                        "type": [
                          "null",
                          "string"
                        ],
                        "description": "An OmniLife occupation id, from GET /occupations or /occupations/categories. Cannot be cleared."
                      },
                      "employmentStatus": {
                        "enum": [
                          "Employee",
                          "SelfEmployed",
                          "Homemaker",
                          "Unemployed",
                          null
                        ],
                        "type": [
                          "null",
                          "string"
                        ],
                        "description": "Employment status. Cannot be cleared."
                      },
                      "supplierOccupationOverrides": {
                        "type": [
                          "null",
                          "object"
                        ],
                        "additionalProperties": {
                          "type": "string"
                        },
                        "description": "Maps a supplier code to that supplier's own occupation code. Merged entry by entry: an entry\nsupplied is added or replaced, an entry set to null is removed, and entries not mentioned\nsurvive. Null for the whole field clears every override."
                      }
                    },
                    "description": "The changeable subset of ClientDetails. The identity fields (`dateOfBirth`, `age`, `gender`)\nlock shortly after the client is created; past that window a change is accepted only when it is\nconsistent with the same person, and rejected with 409 otherwise."
                  },
                  "retention": {
                    "type": "object",
                    "properties": {
                      "timeToLiveDays": {
                        "type": [
                          "null",
                          "integer"
                        ],
                        "description": "Days until the client's PII is purged. Null means never expire.",
                        "format": "int32"
                      },
                      "isSlidingExpiration": {
                        "type": [
                          "null",
                          "boolean"
                        ],
                        "description": "When true, billable activity resets the retention clock. Cannot be cleared."
                      }
                    },
                    "description": "The changeable subset of Retention."
                  },
                  "externalReference": {
                    "type": [
                      "null",
                      "string"
                    ],
                    "description": "The caller's own correlation key. Null clears it."
                  }
                },
                "description": "A partial update to a client, as an RFC 7396 JSON Merge Patch. Omit a field to leave it\nunchanged; pass null to clear it, where the field is clearable - `externalReference`,\n`retention.timeToLiveDays`, or a single `supplierOccupationOverrides` entry. Clearing a field\nthat has no empty state is rejected rather than silently ignored."
              },
              "example": {
                "clientDetails": {
                  "income": 135000
                },
                "externalReference": null
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Returns the client's details after the patch is applied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClientResponse"
                }
              }
            }
          },
          "400": {
            "description": "The patch failed validation, or the request body was not a valid JSON Merge Patch object; the response body lists the invalid fields.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No client exists with the given `clientId`, or it belongs to another API user.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "The patch would change an identity field (`dateOfBirth`/`age`, `gender`) in a way that is not consistent with the same person, more than 30 minutes after the client was created. Create a new client instead.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                },
                "example": {
                  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.10",
                  "title": "Client identity locked",
                  "status": 409,
                  "detail": "This change would alter an identity field (dateOfBirth/age, gender) in a way that isn't consistent with the same person, more than 30 minutes after creation. Create a new client instead.",
                  "code": "identity_locked",
                  "fields": [
                    "dateOfBirth"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "No access token was supplied, or the token is expired or invalid. The response has no body. Request a new token from the token endpoint and retry.",
            "headers": {
              "WWW-Authenticate": {
                "description": "A `Bearer` challenge whose `resource_metadata` names the URL of this API's protected-resource metadata document.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests - the rate limit has been exceeded. Wait for the number of seconds in the Retry-After header before retrying.",
            "headers": {
              "Retry-After": {
                "description": "The number of seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "415": {
            "description": "The body was sent with a content type this operation doesn't accept. Resend it as `application/merge-patch+json` or `application/json`; the problem's `detail` names the accepted types too.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "405": {
            "description": "This path does not accept the method the request used; it accepts `GET`, `PATCH`, `DELETE`. The problem's `code` is `method_not_allowed`. The `Allow` header lists the accepted methods.",
            "headers": {
              "Allow": {
                "description": "The methods this path accepts, comma-separated.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Clients"
        ],
        "summary": "Delete a client",
        "description": "Soft-deletes a client (one-way): the row and its billing history remain, but the client no longer appears in listings and can't be quoted. Returns 404 when the client is missing, belongs to another API user, or is already deleted. Requires a bearer token.",
        "operationId": "DeleteClient",
        "parameters": [
          {
            "name": "clientId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "The client is deleted; there is no response body."
          },
          "400": {
            "description": "The `clientId` path segment is not a UUID; the response body names it.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No client exists with the given `clientId`, it belongs to another API user, or it is already deleted.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "No access token was supplied, or the token is expired or invalid. The response has no body. Request a new token from the token endpoint and retry.",
            "headers": {
              "WWW-Authenticate": {
                "description": "A `Bearer` challenge whose `resource_metadata` names the URL of this API's protected-resource metadata document.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests - the rate limit has been exceeded. Wait for the number of seconds in the Retry-After header before retrying.",
            "headers": {
              "Retry-After": {
                "description": "The number of seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "405": {
            "description": "This path does not accept the method the request used; it accepts `GET`, `PATCH`, `DELETE`. The problem's `code` is `method_not_allowed`. The `Allow` header lists the accepted methods.",
            "headers": {
              "Allow": {
                "description": "The methods this path accepts, comma-separated.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/clients/{clientId}/quotes": {
      "post": {
        "tags": [
          "Quoting"
        ],
        "summary": "Quote premiums for a client",
        "description": "Computes premiums for the given Scenario against the client's supplier set. Stateless: nothing is stored, there is no quoteId, and the same Scenario body re-computes fresh every call. Returns 404 both when the client is missing and when it belongs to another API user, and when it has been deleted. A successful call is a billable event for this client: the first one starts a 60-day exempt period during which further quote, projection and research calls for the same client are not charged again. A client's `exemptUntil` says whether the next call is chargeable. Requires a bearer token.",
        "operationId": "QuotePremiums",
        "parameters": [
          {
            "name": "clientId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Scenario"
              },
              "examples": {
                "minimalLifeOnly": {
                  "value": {
                    "includedSuppliers": [
                      "AIG"
                    ],
                    "quoteOptions": {
                      "includePremiumWaiver": false,
                      "commissionType": "Auto",
                      "paymentFrequency": "Yearly"
                    },
                    "covers": {
                      "life": {
                        "sumInsured": 1000000,
                        "premiumStructure": "VariableAgeStepped",
                        "ownership": "NonSuper"
                      }
                    }
                  }
                },
                "multiCoverWithExtension": {
                  "value": {
                    "includedSuppliers": [
                      "AIG"
                    ],
                    "quoteOptions": {
                      "includePremiumWaiver": false,
                      "commissionType": "Auto",
                      "paymentFrequency": "Monthly"
                    },
                    "covers": {
                      "life": {
                        "sumInsured": 750000,
                        "premiumStructure": "VariableAgeStepped",
                        "ownership": "NonSuper"
                      },
                      "tpdExtensionToLife": {
                        "sumInsured": 250000,
                        "premiumStructure": "VariableAgeStepped",
                        "ownership": "NonSuper",
                        "lifeBuyBack": "Exclude",
                        "doubleTPD": "Exclude",
                        "occupationType": "Any"
                      },
                      "incomeProtection": {
                        "monthlyBenefit": 6000,
                        "superContributionOption": 0,
                        "ownership": "NonSuper",
                        "premiumStructure": "VariableAgeStepped",
                        "accidentBenefit": "Exclude",
                        "increaseClaimBenefit": "Exclude",
                        "waitingPeriod": "Days30",
                        "benefitPeriod": "ToAge65",
                        "initialReplacementRatio": "Any",
                        "features": "Standard"
                      }
                    }
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Returns the premium for each supplier able to price the requested covers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QuoteResponse"
                },
                "examples": {
                  "minimalLifeOnly": {
                    "value": {
                      "quotedAt": "2026-03-10T02:20:00Z",
                      "currency": "AUD",
                      "paymentFrequency": "Yearly",
                      "quotedAge": 36,
                      "results": [
                        {
                          "supplierCode": "AIG",
                          "supplierName": "AIA",
                          "allNeedsMet": true,
                          "featureScore": 100,
                          "occupation": "Medical Practitioner",
                          "commission": {
                            "adviserUpfront": 241.23,
                            "adviserOngoing": 80.41,
                            "adviserUpfrontPercentage": 66,
                            "adviserOngoingPercentage": 22
                          },
                          "premiumTotal": {
                            "premium": 365.50,
                            "stampDuty": 0,
                            "totalPremium": 365.50,
                            "possibleRolloverRebate": 0,
                            "byOwnership": {
                              "insideSuper": {
                                "premium": 0,
                                "stampDuty": 0,
                                "totalPremium": 0
                              },
                              "outsideSuper": {
                                "premium": 365.50,
                                "stampDuty": 0,
                                "totalPremium": 365.50
                              }
                            }
                          },
                          "covers": {
                            "policyFee": {
                              "premium": 0,
                              "stampDuty": 0,
                              "totalPremium": 0,
                              "possibleRolloverRebate": 0,
                              "byOwnership": {
                                "insideSuper": {
                                  "premium": 0,
                                  "stampDuty": 0,
                                  "totalPremium": 0
                                },
                                "outsideSuper": {
                                  "premium": 0,
                                  "stampDuty": 0,
                                  "totalPremium": 0
                                }
                              }
                            },
                            "life": {
                              "product": {
                                "code": "AIG1",
                                "name": "Life Cover Plan"
                              },
                              "premium": 365.50,
                              "stampDuty": 0,
                              "totalPremium": 365.50,
                              "possibleRolloverRebate": 0,
                              "byOwnership": {
                                "insideSuper": {
                                  "premium": 0,
                                  "stampDuty": 0,
                                  "totalPremium": 0
                                },
                                "outsideSuper": {
                                  "premium": 365.50,
                                  "stampDuty": 0,
                                  "totalPremium": 365.50
                                }
                              }
                            }
                          },
                          "errors": [ ]
                        }
                      ]
                    }
                  },
                  "oneSupplierCouldNotMeetEveryCover": {
                    "value": {
                      "quotedAt": "2026-03-10T02:25:00Z",
                      "referenceId": "req-9001",
                      "currency": "AUD",
                      "paymentFrequency": "Monthly",
                      "quotedAge": 36,
                      "results": [
                        {
                          "supplierCode": "AIG",
                          "supplierName": "AIA",
                          "allNeedsMet": true,
                          "featureScore": 92,
                          "occupation": "Medical Practitioner",
                          "commission": {
                            "adviserUpfront": 87.66,
                            "adviserOngoing": 29.23,
                            "adviserUpfrontPercentage": 66,
                            "adviserOngoingPercentage": 22
                          },
                          "premiumTotal": {
                            "premium": 143.45,
                            "stampDuty": 4.51,
                            "totalPremium": 147.96,
                            "possibleRolloverRebate": 0,
                            "byOwnership": {
                              "insideSuper": {
                                "premium": 0,
                                "stampDuty": 0,
                                "totalPremium": 0
                              },
                              "outsideSuper": {
                                "premium": 143.45,
                                "stampDuty": 4.51,
                                "totalPremium": 147.96
                              }
                            }
                          },
                          "covers": {
                            "policyFee": {
                              "premium": 0,
                              "stampDuty": 0,
                              "totalPremium": 0,
                              "possibleRolloverRebate": 0,
                              "byOwnership": {
                                "insideSuper": {
                                  "premium": 0,
                                  "stampDuty": 0,
                                  "totalPremium": 0
                                },
                                "outsideSuper": {
                                  "premium": 0,
                                  "stampDuty": 0,
                                  "totalPremium": 0
                                }
                              }
                            },
                            "life": {
                              "product": {
                                "code": "AIG1",
                                "name": "Life Cover Plan - TPD"
                              },
                              "premium": 28.15,
                              "stampDuty": 0,
                              "totalPremium": 28.15,
                              "possibleRolloverRebate": 0,
                              "byOwnership": {
                                "insideSuper": {
                                  "premium": 0,
                                  "stampDuty": 0,
                                  "totalPremium": 0
                                },
                                "outsideSuper": {
                                  "premium": 28.15,
                                  "stampDuty": 0,
                                  "totalPremium": 28.15
                                }
                              }
                            },
                            "tpdExtensionToLife": {
                              "product": {
                                "code": "AIG1",
                                "name": "Life Cover Plan - TPD"
                              },
                              "premium": 25.17,
                              "stampDuty": 0,
                              "totalPremium": 25.17,
                              "possibleRolloverRebate": 0,
                              "byOwnership": {
                                "insideSuper": {
                                  "premium": 0,
                                  "stampDuty": 0,
                                  "totalPremium": 0
                                },
                                "outsideSuper": {
                                  "premium": 25.17,
                                  "stampDuty": 0,
                                  "totalPremium": 25.17
                                }
                              }
                            },
                            "incomeProtection": {
                              "product": {
                                "code": "AIGC1",
                                "name": "Income Protection CORE"
                              },
                              "premium": 90.13,
                              "stampDuty": 4.51,
                              "totalPremium": 94.64,
                              "possibleRolloverRebate": 0,
                              "byOwnership": {
                                "insideSuper": {
                                  "premium": 0,
                                  "stampDuty": 0,
                                  "totalPremium": 0
                                },
                                "outsideSuper": {
                                  "premium": 90.13,
                                  "stampDuty": 4.51,
                                  "totalPremium": 94.64
                                }
                              }
                            }
                          },
                          "errors": [ ]
                        },
                        {
                          "supplierCode": "PPS",
                          "supplierName": "PPS Mutual",
                          "allNeedsMet": false,
                          "errors": [
                            {
                              "level": "Cover",
                              "code": "no_product_meets_need",
                              "cover": "tpdExtensionToLife",
                              "message": "No product could meet the tpdExtensionToLife need.",
                              "excludedProducts": [
                                {
                                  "product": "PPS1",
                                  "code": "PPS1",
                                  "name": "Life Cover - TPD - Trauma",
                                  "reason": "This product does not support the selected buy-back option - Exclude.\nThis product does not support the selected double TPD option - Exclude."
                                }
                              ]
                            }
                          ]
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request failed validation; the response body lists the invalid fields.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No client exists with the given `clientId`, it belongs to another API user, or it has been deleted.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "422": {
            "description": "A value in the stored client or the scenario could not be accepted for pricing - most often an `occupationId` that is no longer a recognised occupation. `code` says which, and `errors` names the field where it can be traced to one. Retrying unchanged fails the same way. Not billed.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "502": {
            "description": "The pricing engine could not be reached (`omnilife_unavailable` - retry the request) or answered with something this API could not interpret (`omnilife_response_invalid` - retrying is unlikely to help). Not billed for this client either way.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "No access token was supplied, or the token is expired or invalid. The response has no body. Request a new token from the token endpoint and retry.",
            "headers": {
              "WWW-Authenticate": {
                "description": "A `Bearer` challenge whose `resource_metadata` names the URL of this API's protected-resource metadata document.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests - the rate limit has been exceeded. Wait for the number of seconds in the Retry-After header before retrying.",
            "headers": {
              "Retry-After": {
                "description": "The number of seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "415": {
            "description": "The body was sent with a content type this operation doesn't accept. Resend it as `application/json`; the problem's `detail` names the accepted types too.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "405": {
            "description": "This path does not accept the method the request used; it accepts `POST`. The problem's `code` is `method_not_allowed`. The `Allow` header lists the accepted methods.",
            "headers": {
              "Allow": {
                "description": "The methods this path accepts, comma-separated.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/clients/{clientId}/projections": {
      "post": {
        "tags": [
          "Quoting"
        ],
        "summary": "Project future premiums for a client",
        "description": "Computes the projected total premium per year for the given Scenario against the client's supplier set, over ?years (default 5) with ?indexationRate applied to sum insured (default 0, range 0-0.1). Stateless: nothing is stored and the same Scenario body re-computes fresh every call. Returns 404 both when the client is missing and when it belongs to another API user, and when it has been deleted. A successful call is a billable event for this client: the first one starts a 60-day exempt period during which further quote, projection and research calls for the same client are not charged again. A client's `exemptUntil` says whether the next call is chargeable. Requires a bearer token.",
        "operationId": "ProjectPremiums",
        "parameters": [
          {
            "name": "clientId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "years",
            "in": "query",
            "schema": {
              "maximum": 2147483647,
              "minimum": 1,
              "type": "integer",
              "format": "int32",
              "default": 5
            }
          },
          {
            "name": "indexationRate",
            "in": "query",
            "schema": {
              "maximum": 0.1,
              "minimum": 0,
              "type": "number",
              "format": "double",
              "default": 0
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Scenario"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Returns the projected total premium per year for each supplier able to price the requested covers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectionResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request failed validation; the response body lists the invalid fields.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No client exists with the given `clientId`, it belongs to another API user, or it has been deleted.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "422": {
            "description": "A value in the stored client or the scenario could not be accepted for pricing - most often an `occupationId` that is no longer a recognised occupation. `code` says which, and `errors` names the field where it can be traced to one. Retrying unchanged fails the same way. Not billed.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "502": {
            "description": "The pricing engine could not be reached (`omnilife_unavailable` - retry the request) or answered with something this API could not interpret (`omnilife_response_invalid` - retrying is unlikely to help). Not billed for this client either way.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "No access token was supplied, or the token is expired or invalid. The response has no body. Request a new token from the token endpoint and retry.",
            "headers": {
              "WWW-Authenticate": {
                "description": "A `Bearer` challenge whose `resource_metadata` names the URL of this API's protected-resource metadata document.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests - the rate limit has been exceeded. Wait for the number of seconds in the Retry-After header before retrying.",
            "headers": {
              "Retry-After": {
                "description": "The number of seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "415": {
            "description": "The body was sent with a content type this operation doesn't accept. Resend it as `application/json`; the problem's `detail` names the accepted types too.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "405": {
            "description": "This path does not accept the method the request used; it accepts `POST`. The problem's `code` is `method_not_allowed`. The `Allow` header lists the accepted methods.",
            "headers": {
              "Allow": {
                "description": "The methods this path accepts, comma-separated.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/clients/{clientId}/research": {
      "post": {
        "tags": [
          "Quoting"
        ],
        "summary": "Research feature support for a client",
        "description": "Computes a feature matrix for the given Scenario against the client's supplier set: per requested cover, which suppliers support each feature. No score - the single portfolio score is on the premium response. Stateless: nothing is stored and the same Scenario body re-computes fresh every call. Returns 404 both when the client is missing and when it belongs to another API user, and when it has been deleted. A successful call is a billable event for this client: the first one starts a 60-day exempt period during which further quote, projection and research calls for the same client are not charged again. A client's `exemptUntil` says whether the next call is chargeable. Requires a bearer token.",
        "operationId": "ResearchFeatures",
        "parameters": [
          {
            "name": "clientId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Scenario"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Returns, per requested cover, which suppliers support each feature.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResearchResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request failed validation; the response body lists the invalid fields.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "No client exists with the given `clientId`, it belongs to another API user, or it has been deleted.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "422": {
            "description": "A value in the stored client or the scenario could not be accepted for pricing - most often an `occupationId` that is no longer a recognised occupation. `code` says which, and `errors` names the field where it can be traced to one. Retrying unchanged fails the same way. Not billed.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "502": {
            "description": "The pricing engine could not be reached (`omnilife_unavailable` - retry the request) or answered with something this API could not interpret (`omnilife_response_invalid` - retrying is unlikely to help). Not billed for this client either way.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "No access token was supplied, or the token is expired or invalid. The response has no body. Request a new token from the token endpoint and retry.",
            "headers": {
              "WWW-Authenticate": {
                "description": "A `Bearer` challenge whose `resource_metadata` names the URL of this API's protected-resource metadata document.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests - the rate limit has been exceeded. Wait for the number of seconds in the Retry-After header before retrying.",
            "headers": {
              "Retry-After": {
                "description": "The number of seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "415": {
            "description": "The body was sent with a content type this operation doesn't accept. Resend it as `application/json`; the problem's `detail` names the accepted types too.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "405": {
            "description": "This path does not accept the method the request used; it accepts `POST`. The problem's `code` is `method_not_allowed`. The `Allow` header lists the accepted methods.",
            "headers": {
              "Allow": {
                "description": "The methods this path accepts, comma-separated.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/billing/periods": {
      "get": {
        "tags": [
          "Billing"
        ],
        "summary": "Billing history by month",
        "description": "Returns the calling API user's monthly billing rollups, newest-first and dense: one entry per month from the provisioning month through the current month, including zero-activity months. Each entry reports how many distinct clients were Billable, Exempt, and serviced in total (total = billable + exempt). The first entry is the in-progress current month, so its counts grow until month-end. Figures are indicative, not an invoice - refer to your MSA for actual billing terms. Requires a bearer token.",
        "operationId": "ListBillingPeriods",
        "responses": {
          "200": {
            "description": "Returns one billing rollup entry per month from the provisioning month through the current month.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/BillingPeriodResponse"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No access token was supplied, or the token is expired or invalid. The response has no body. Request a new token from the token endpoint and retry.",
            "headers": {
              "WWW-Authenticate": {
                "description": "A `Bearer` challenge whose `resource_metadata` names the URL of this API's protected-resource metadata document.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests - the rate limit has been exceeded. Wait for the number of seconds in the Retry-After header before retrying.",
            "headers": {
              "Retry-After": {
                "description": "The number of seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "405": {
            "description": "This path does not accept the method the request used; it accepts `GET`. The problem's `code` is `method_not_allowed`. The `Allow` header lists the accepted methods.",
            "headers": {
              "Allow": {
                "description": "The methods this path accepts, comma-separated.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/billing/periods/{month}": {
      "get": {
        "tags": [
          "Billing"
        ],
        "summary": "One month's billing rollup",
        "description": "Returns a single month's billing rollup. The `month` path segment is `yyyy-MM` (e.g. 2026-07). Returns 400 when it is malformed and 404 when it falls outside the range [provisioning month, current month]. Requires a bearer token.",
        "operationId": "GetBillingPeriod",
        "parameters": [
          {
            "name": "month",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Returns the billing rollup for the requested month.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BillingPeriodResponse"
                }
              }
            }
          },
          "400": {
            "description": "The `month` path segment is not in `yyyy-MM` format.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "The requested month falls outside the caller's billing history (before the provisioning month, or after the current month).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "No access token was supplied, or the token is expired or invalid. The response has no body. Request a new token from the token endpoint and retry.",
            "headers": {
              "WWW-Authenticate": {
                "description": "A `Bearer` challenge whose `resource_metadata` names the URL of this API's protected-resource metadata document.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests - the rate limit has been exceeded. Wait for the number of seconds in the Retry-After header before retrying.",
            "headers": {
              "Retry-After": {
                "description": "The number of seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "405": {
            "description": "This path does not accept the method the request used; it accepts `GET`. The problem's `code` is `method_not_allowed`. The `Allow` header lists the accepted methods.",
            "headers": {
              "Allow": {
                "description": "The methods this path accepts, comma-separated.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/billing/periods/{month}/clients": {
      "get": {
        "tags": [
          "Billing"
        ],
        "summary": "Clients serviced in a month",
        "description": "Returns the clients serviced in the given month (`yyyy-MM`), each marked Billable or Exempt. Cursor-based pagination: pass the previous response's `nextCursor` as the `cursor` query parameter; `pageSize` defaults to 50 and is capped at 200. Optional `status` filter (Billable|Exempt). Soft-deleted and PII-purged clients still appear; `externalReference` is best-effort (null once a client's PII has been purged). Returns 400 on a malformed month, cursor, pageSize, or status, and 404 when the month is out of range. Requires a bearer token.",
        "operationId": "ListBillingPeriodClients",
        "parameters": [
          {
            "name": "month",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Filters to clients with this billing status for the month (`Billable` or `Exempt`, case-insensitive). Omit to return both.",
            "schema": {
              "enum": [
                "Billable",
                "Exempt"
              ],
              "type": "string",
              "description": "Filters to clients with this billing status for the month (`Billable` or `Exempt`, case-insensitive). Omit to return both."
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Returns a page of the clients serviced in the requested month, each marked Billable or Exempt.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PagedResultOfBillingClientEntry"
                }
              }
            }
          },
          "400": {
            "description": "The `month`, `cursor`, `pageSize`, or `status` parameter is invalid; the response body lists the invalid fields.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "The requested month falls outside the caller's billing history.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "No access token was supplied, or the token is expired or invalid. The response has no body. Request a new token from the token endpoint and retry.",
            "headers": {
              "WWW-Authenticate": {
                "description": "A `Bearer` challenge whose `resource_metadata` names the URL of this API's protected-resource metadata document.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests - the rate limit has been exceeded. Wait for the number of seconds in the Retry-After header before retrying.",
            "headers": {
              "Retry-After": {
                "description": "The number of seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "405": {
            "description": "This path does not accept the method the request used; it accepts `GET`. The problem's `code` is `method_not_allowed`. The `Allow` header lists the accepted methods.",
            "headers": {
              "Allow": {
                "description": "The methods this path accepts, comma-separated.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/token": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Exchange client credentials for an access token",
        "description": "OAuth2 client-credentials grant (RFC 6749 §4.4): POST a form-encoded request with grant_type=client_credentials, client_id and client_secret (client_secret_post) to receive a short-lived bearer JWT (~1h, no refresh). The JWT is required on every other endpoint (deny-by-default). Optionally pass an RFC 8707 resource indicator; it must name this API's canonical resource. Errors use RFC 6749 §5.2 shapes.",
        "operationId": "IssueToken",
        "requestBody": {
          "description": "Form-encoded client-credentials grant (application/x-www-form-urlencoded). A non-form body is rejected with 415.",
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "required": [
                  "grant_type",
                  "client_id",
                  "client_secret"
                ],
                "type": "object",
                "properties": {
                  "grant_type": {
                    "enum": [
                      "client_credentials"
                    ],
                    "type": "string",
                    "description": "The OAuth2 grant type. `client_credentials` (RFC 6749 §4.4) is the only one supported."
                  },
                  "client_id": {
                    "type": "string",
                    "description": "The client identifier issued at provisioning."
                  },
                  "client_secret": {
                    "type": "string",
                    "description": "The client secret issued at provisioning (client_secret_post).",
                    "format": "password"
                  },
                  "resource": {
                    "enum": [
                      "https://simple.omnilife.com.au"
                    ],
                    "type": "string",
                    "description": "Optional RFC 8707 resource indicator. When present it must be `https://simple.omnilife.com.au`, this API's canonical resource URL, else the request is rejected with invalid_target. The same value is published as `resource` by /.well-known/oauth-protected-resource.",
                    "format": "uri"
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Returns the bearer access token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthTokenResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request is malformed: a missing or unsupported `grant_type`, missing `client_id`/`client_secret`, or an unrecognised `resource` indicator.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "The `client_id` or `client_secret` is incorrect, or the account is inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests - the rate limit has been exceeded. Wait for the number of seconds in the Retry-After header before retrying.",
            "headers": {
              "Retry-After": {
                "description": "The number of seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "415": {
            "description": "The body was sent with a content type this operation doesn't accept. Resend it as `application/x-www-form-urlencoded`; the body is an `invalid_request` error whose `error_description` names the accepted type too.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthErrorResponse"
                }
              }
            }
          },
          "405": {
            "description": "This path does not accept the method the request used; it accepts `POST`. The body is an `invalid_request` error. The `Allow` header lists the accepted methods.",
            "headers": {
              "Allow": {
                "description": "The methods this path accepts, comma-separated.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthErrorResponse"
                }
              }
            }
          }
        },
        "security": [ ]
      }
    }
  },
  "components": {
    "schemas": {
      "AustralianState": {
        "enum": [
          "ACT",
          "NSW",
          "NT",
          "QLD",
          "SA",
          "TAS",
          "VIC",
          "WA"
        ],
        "description": "The client's state or territory of residence, used to calculate stamp duty."
      },
      "BabyCare": {
        "enum": [
          "Include",
          "Exclude"
        ],
        "description": "Extends trauma cover to specified congenital conditions in a newborn child of the insured."
      },
      "BeBenefitPeriod": {
        "enum": [
          "Years1"
        ],
        "description": "How long Business Expenses keeps paying a claim once it starts."
      },
      "BePremiumStructure": {
        "enum": [
          "VariableAgeStepped",
          "Variable"
        ],
        "description": "Premium structure for the business expenses cover. `VariableAgeStepped` premiums rise with age;\n`Variable` premiums stay level."
      },
      "BeWaitingPeriod": {
        "enum": [
          "Days14",
          "Days30",
          "Days60",
          "Days90"
        ],
        "description": "How many days of disability before Business Expenses starts paying a claim."
      },
      "BillingClientEntry": {
        "required": [
          "clientId",
          "status"
        ],
        "type": "object",
        "properties": {
          "clientId": {
            "type": "string",
            "description": "The serviced Client's id. Always present, even after PII purge or soft delete.",
            "format": "uuid"
          },
          "externalReference": {
            "type": [
              "null",
              "string"
            ],
            "description": "The caller's own correlation key, for reconciliation against their system, when available.\nBest-effort: `null` for a client that never had one or whose PII has been\npurged."
          },
          "status": {
            "description": "Whether this client is charged (`Billable`) this month.",
            "$ref": "#/components/schemas/BillingStatus"
          },
          "exemptUntil": {
            "type": [
              "null",
              "string"
            ],
            "description": "The last Sydney calendar date of the exempt period that covered this client during this\nentry's billing month - the exempt period governing `that` month, not necessarily the one\nrunning today, so a historical month keeps reporting the same date on repeat calls.\nPopulated whenever the client has a Billable Event at or before this month (so it is set\non a `Billable` entry too, not only `Exempt`); `null` only for a client\nwith no prior Billable Event.",
            "format": "date"
          }
        },
        "description": "One serviced Client within a billing period, with its derived BillingStatus."
      },
      "BillingPeriodResponse": {
        "required": [
          "month",
          "billable",
          "exempt",
          "total"
        ],
        "type": "object",
        "properties": {
          "month": {
            "type": "string",
            "description": "The calendar month, `yyyy-MM`, as a Sydney calendar month."
          },
          "billable": {
            "type": "integer",
            "description": "Distinct clients charged this month.",
            "format": "int32"
          },
          "exempt": {
            "type": "integer",
            "description": "Distinct clients serviced this month but not charged (under an exempt period).",
            "format": "int32"
          },
          "total": {
            "type": "integer",
            "description": "Distinct clients serviced this month. Always equals `Billable + Exempt`.",
            "format": "int32"
          }
        },
        "description": "One calendar month's billing rollup for the calling API user. Indicative - not an invoice;\nrefer to the Master Services Agreement (MSA) for actual billing terms."
      },
      "BillingStatus": {
        "enum": [
          "Billable",
          "Exempt"
        ],
        "description": "Whether servicing a client in a billing period is charged: `Billable`, or `Exempt` because the\nclient was serviced inside a 60-day exempt period started by an earlier billable event."
      },
      "BusinessExpenses": {
        "required": [
          "monthlyBenefit"
        ],
        "type": "object",
        "properties": {
          "monthlyBenefit": {
            "maximum": 2147483647,
            "minimum": 1,
            "type": "integer",
            "description": "The monthly business-expense reimbursement paid while a claim is open, in AUD.",
            "format": "int32"
          },
          "premiumStructure": {
            "default": "VariableAgeStepped",
            "$ref": "#/components/schemas/BePremiumStructure"
          },
          "waitingPeriod": {
            "default": "Days30",
            "$ref": "#/components/schemas/BeWaitingPeriod"
          },
          "benefitPeriod": {
            "default": "Years1",
            "$ref": "#/components/schemas/BeBenefitPeriod"
          }
        },
        "description": "Business Expenses: reimburses the client's fixed business expenses while a claim is open."
      },
      "ChildGender": {
        "enum": [
          "Male",
          "Female"
        ],
        "description": "The insured child's gender."
      },
      "ChildTrauma": {
        "required": [
          "sumInsured",
          "gender"
        ],
        "type": "object",
        "properties": {
          "sumInsured": {
            "maximum": 2147483647,
            "minimum": 1,
            "type": "integer",
            "description": "The lump sum paid on a claim for this child, in AUD.",
            "format": "int32"
          },
          "dateOfBirth": {
            "type": [
              "null",
              "string"
            ],
            "description": "The child's date of birth. Supply this or `age`, not both.",
            "format": "date"
          },
          "age": {
            "maximum": 18,
            "minimum": 0,
            "type": [
              "null",
              "integer"
            ],
            "description": "The child's age in whole years. Supply this or `dateOfBirth`, not both.",
            "format": "int32"
          },
          "gender": {
            "$ref": "#/components/schemas/ChildGender"
          }
        },
        "description": "One insured child under Child Trauma, which pays a lump sum on diagnosis of a specified\nchildhood critical illness or injury. Exactly one of `dateOfBirth` or `age` must be supplied,\nmirroring the client's own date-of-birth-or-age rule."
      },
      "ClientDetails": {
        "required": [
          "gender",
          "australianState",
          "smokerStatus",
          "occupationId"
        ],
        "type": "object",
        "properties": {
          "dateOfBirth": {
            "type": [
              "null",
              "string"
            ],
            "description": "Date of birth (preferred). Either this or `age` is required; `dateOfBirth` wins.",
            "format": "date"
          },
          "age": {
            "maximum": 120,
            "minimum": 0,
            "type": [
              "null",
              "integer"
            ],
            "description": "Age in years. Used only when `dateOfBirth` is not supplied.",
            "format": "int32"
          },
          "gender": {
            "$ref": "#/components/schemas/Gender"
          },
          "australianState": {
            "$ref": "#/components/schemas/AustralianState"
          },
          "smokerStatus": {
            "$ref": "#/components/schemas/SmokerStatus"
          },
          "income": {
            "maximum": 2147483647,
            "minimum": 0,
            "type": "integer",
            "description": "Annual income excluding super, in dollars.",
            "format": "int32",
            "example": 120000
          },
          "occupationId": {
            "type": "string",
            "description": "An occupation id, from GET /occupations or /occupations/categories. Digits only, exactly as\nthose endpoints return it (e.g. `20025`) - no zero-padding.",
            "example": 20025
          },
          "employmentStatus": {
            "default": "Employee",
            "$ref": "#/components/schemas/EmploymentStatus"
          },
          "supplierOccupationOverrides": {
            "type": [
              "null",
              "object"
            ],
            "additionalProperties": {
              "type": "string"
            },
            "description": "Optional advanced override: maps a supplier code to that supplier's own occupation code.\nEach key is a three-capital-letter supplier code from GET /suppliers (e.g. `ACC`)."
          }
        },
        "description": "The life-insured person's details."
      },
      "ClientResponse": {
        "required": [
          "clientId",
          "clientDetails",
          "retention",
          "createdAt",
          "updatedAt"
        ],
        "type": "object",
        "properties": {
          "clientId": {
            "type": "string",
            "description": "The client's id, used in every `/clients/{clientId}` path.",
            "format": "uuid"
          },
          "clientDetails": {
            "$ref": "#/components/schemas/ClientDetails"
          },
          "retention": {
            "$ref": "#/components/schemas/Retention"
          },
          "externalReference": {
            "type": [
              "null",
              "string"
            ],
            "description": "The caller-supplied correlation key, if one was set."
          },
          "createdAt": {
            "type": "string",
            "description": "When the client was created (UTC).",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "description": "When the client's data was last changed by the caller (create or PATCH), UTC. Not moved by\nsystem activity like the retention slide, so it's a stable watermark for `updatedAfter`\ndelta-sync polling.",
            "format": "date-time"
          },
          "ageAsAt": {
            "type": [
              "null",
              "string"
            ],
            "description": "When `age` was last asserted by the caller (create, or any PATCH whose resulting details\ncarry `age` - even unchanged), UTC. Null for a DOB client: its age isn't an estimate,\ncallers hold the date of birth.",
            "format": "date-time"
          },
          "estimatedCurrentAge": {
            "type": [
              "null",
              "integer"
            ],
            "description": "The estimated current age for an age-only client: the stored `age` plus whole years\nelapsed since `ageAsAt` (the capture-anniversary convention - the estimate never\noverstates the caller's assertion). Null for a DOB client. This is what a quote,\nprojection or research call would price today; see each response's `quotedAge` for what a\ngiven call actually used.",
            "format": "int32"
          },
          "exemptUntil": {
            "type": [
              "null",
              "string"
            ],
            "description": "The last Sydney calendar date of the client's currently-running 60-day exempt period, or\n`null` when there is none - `null` means the next quote, projection or\nresearch call for this client will be billed. An expired period is normalised to\n`null` here; a past date is never returned as an exemption. Indicative\nonly, since the underlying billing write is best-effort.",
            "format": "date"
          }
        },
        "description": "A persisted client, in Simple's readable shape."
      },
      "CommissionResult": {
        "type": "object",
        "properties": {
          "adviserUpfront": {
            "type": "number",
            "description": "Upfront commission paid to the adviser in year one (dollars, at the quote's payment frequency).",
            "format": "double"
          },
          "adviserOngoing": {
            "type": "number",
            "description": "Ongoing commission paid to the adviser in subsequent years (dollars, at the quote's payment frequency).",
            "format": "double"
          },
          "adviserUpfrontPercentage": {
            "type": "number",
            "description": "Upfront commission rate as a whole-number percentage (`66` means 66%), not a fraction.",
            "format": "double"
          },
          "adviserOngoingPercentage": {
            "type": "number",
            "description": "Ongoing commission rate as a whole-number percentage (`22` means 22%), not a fraction.",
            "format": "double"
          }
        },
        "description": "The adviser commission on one supplier's premium, in AUD and as a percentage. Every figure is\ncalculated by the insurer's own commission rules, which differ between insurers in what they\ninclude, and is reported as is - so don't derive one figure from another (the dollar amount is\nnot necessarily the percentage applied to `premium`)."
      },
      "CommissionType": {
        "enum": [
          "Auto",
          "Hybrid",
          "Level",
          "None"
        ],
        "description": "A scenario-level choice: `Auto` (resolves per supplier - `Hybrid` while the client is under\nthat insurer's `Hybrid` age limit, else `Level`, so older clients are never silently gated by\nthe insurer's default), `Hybrid`, `Level`, or `None` (nil commission, the lowest premium)."
      },
      "CoverEnd": {
        "type": "object",
        "properties": {
          "cover": {
            "type": "string",
            "description": "The cover key that ends (e.g. \"incomeProtection\")."
          },
          "year": {
            "type": "integer",
            "description": "Projection year in which the cover ends.",
            "format": "int32"
          }
        },
        "description": "A cover that stops being priced within the projection window."
      },
      "CoverFeatures": {
        "type": "object",
        "properties": {
          "features": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FeatureSupport"
            },
            "description": "One entry per feature this cover was compared on."
          }
        },
        "description": "The features compared for one cover."
      },
      "CoverResult": {
        "type": "object",
        "properties": {
          "product": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "description": "The product used for this cover. Extensions carry the SAME product code as their parent\ncover, so \"this extension belongs to that product\" is discoverable without nesting.",
                "$ref": "#/components/schemas/QuoteProduct"
              }
            ]
          },
          "premium": {
            "type": "number",
            "description": "Premium excluding stamp duty, with rollover NOT assumed.",
            "format": "double"
          },
          "stampDuty": {
            "type": "number",
            "description": "Stamp duty payable. Added to `premium` to get `totalPremium`.",
            "format": "double"
          },
          "totalPremium": {
            "type": "number",
            "description": "What is actually payable: `Premium + StampDuty`.",
            "format": "double"
          },
          "possibleRolloverRebate": {
            "type": "number",
            "description": "Potential saving if the client is eligible for rollover. Rollover-discounted price =\n`TotalPremium - PossibleRolloverRebate`. Includes the stamp duty the discount also saves\n(duty is charged on the discounted premium), so it can exceed 15% of `premium`.\n\"Possible\" because eligibility depends on the client's super situation.",
            "format": "double"
          },
          "byOwnership": {
            "description": "The same figures split by ownership (inside vs outside super), also with rollover not\nassumed. A rollover saving only applies to cover held inside super, so the whole\n`possibleRolloverRebate` sits on the `insideSuper` side; `outsideSuper` never includes any of it.",
            "$ref": "#/components/schemas/OwnershipSplit"
          }
        },
        "description": "One cover's result: the product that priced it plus the shared premium breakdown (the\nbreakdown fields appear flat alongside Product in the JSON)."
      },
      "CreateClientRequest": {
        "required": [
          "clientDetails"
        ],
        "type": "object",
        "properties": {
          "clientDetails": {
            "$ref": "#/components/schemas/ClientDetails"
          },
          "retention": {
            "$ref": "#/components/schemas/Retention"
          },
          "externalReference": {
            "maxLength": 128,
            "type": [
              "null",
              "string"
            ],
            "description": "Optional caller-supplied correlation key linking this client to the consumer's own system.\nOpaque to Simple and not unique; filterable via `GET /clients?externalReference=`."
          }
        },
        "description": "Body of `POST /clients`."
      },
      "DoubleTPD": {
        "enum": [
          "Include",
          "Exclude"
        ],
        "description": "Whether TPD Extension to Life pays out twice - once under TPD, later again under Life. Increases the premium."
      },
      "DoubleTrauma": {
        "enum": [
          "Include",
          "Exclude"
        ],
        "description": "Whether Trauma Extension to Life pays out twice - once under trauma, later again under Life. Increases the premium."
      },
      "EmploymentStatus": {
        "enum": [
          "Employee",
          "SelfEmployed",
          "Homemaker",
          "Unemployed"
        ],
        "description": "How the client is employed. Only SelfEmployed materially changes pricing (sole-trader rates\nwith some insurers); the others ride employee rates where the occupation qualifies."
      },
      "ExcludedProduct": {
        "type": "object",
        "properties": {
          "product": {
            "type": "string",
            "description": "The excluded product's code. Carries the same value as `code`; kept for compatibility and\nwill be removed in a future version - use `code` instead.",
            "deprecated": true
          },
          "code": {
            "type": "string",
            "description": "The excluded product's code."
          },
          "name": {
            "type": [
              "null",
              "string"
            ],
            "description": "The excluded product's name. Null when `code` could not be matched to a known product."
          },
          "reason": {
            "type": "string",
            "description": "Why this product could not meet the cover."
          }
        },
        "description": "A product that was excluded from consideration for a cover, with the reason why."
      },
      "FeatureSupport": {
        "type": "object",
        "properties": {
          "feature": {
            "type": "string",
            "description": "The feature's name."
          },
          "supportedBy": {
            "type": "object",
            "additionalProperties": {
              "type": "boolean"
            },
            "description": "Supplier code → whether that supplier's result supports the feature."
          }
        },
        "description": "Whether each supplier's product supports one named feature."
      },
      "Gender": {
        "enum": [
          "Male",
          "Female"
        ],
        "description": "The client's gender, used in premium rating."
      },
      "HttpValidationProblemDetails": {
        "type": "object",
        "properties": {
          "type": {
            "type": [
              "null",
              "string"
            ],
            "description": "A URI identifying the kind of problem. Informational only - `code` is the member to branch on."
          },
          "title": {
            "type": [
              "null",
              "string"
            ],
            "description": "A short summary of the kind of problem, in English and the same for every occurrence of one `code`."
          },
          "status": {
            "type": [
              "null",
              "integer"
            ],
            "description": "The HTTP status code, repeated in the body.",
            "format": "int32"
          },
          "detail": {
            "type": [
              "null",
              "string"
            ],
            "description": "What went wrong on this particular request, written for a person. Not stable between releases and not meant to be parsed."
          },
          "instance": {
            "type": [
              "null",
              "string"
            ],
            "description": "The path of the request that failed."
          },
          "errors": {
            "type": "object",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "description": "One entry per rejected field, keyed by that field's path in the request (for example `covers.incomeProtection.superContributionOption`, or the name of a query parameter), with one or more messages saying why it was rejected."
          },
          "code": {
            "$ref": "#/components/schemas/ProblemCode"
          },
          "correlationId": {
            "type": [
              "null",
              "string"
            ],
            "description": "The request's correlation id, quoted to support to have this exact request traced in the server logs. Carried in the body on the failures where support is the next step rather than a change to the request. The same id is returned on the `X-Correlation-ID` response header of every response, so it can always be read from there instead; supplying that header on a request adopts the given id."
          },
          "fields": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "string"
            },
            "description": "Present only on an `identity_locked` conflict: the client fields whose change was refused."
          }
        },
        "description": "The problem document returned when a request fails validation, adding `errors` to name the fields at fault. Its `code` is always `validation_failed`."
      },
      "IncomeProtection": {
        "required": [
          "monthlyBenefit"
        ],
        "type": "object",
        "properties": {
          "monthlyBenefit": {
            "maximum": 2147483647,
            "minimum": 1,
            "type": "integer",
            "description": "The monthly income paid while a claim is open, in AUD.",
            "format": "int32"
          },
          "superContributionOption": {
            "type": "integer",
            "description": "Super contribution option, as a dollar amount. Capped at 15% of the client's monthly income\n(`income / 12 * 0.15`) - not 15% of `monthlyBenefit`, which is the insured benefit amount,\nnot the client's actual income.",
            "format": "int32",
            "default": 0
          },
          "ownership": {
            "default": "NonSuper",
            "$ref": "#/components/schemas/IpOwnership"
          },
          "premiumStructure": {
            "default": "VariableAgeStepped",
            "$ref": "#/components/schemas/IpPremiumStructure"
          },
          "accidentBenefit": {
            "default": "Exclude",
            "$ref": "#/components/schemas/IpAccidentBenefit"
          },
          "increaseClaimBenefit": {
            "default": "Exclude",
            "$ref": "#/components/schemas/IpIncreaseClaimBenefit"
          },
          "waitingPeriod": {
            "default": "Days30",
            "$ref": "#/components/schemas/IpWaitingPeriod"
          },
          "benefitPeriod": {
            "default": "ToAge65",
            "$ref": "#/components/schemas/IpBenefitPeriod"
          },
          "initialReplacementRatio": {
            "default": "Any",
            "$ref": "#/components/schemas/IpReplacementRatio"
          },
          "features": {
            "default": "Standard",
            "$ref": "#/components/schemas/IpFeature"
          }
        },
        "description": "Income Protection: replaces a portion of the client's income while a claim is open."
      },
      "IpAccidentBenefit": {
        "enum": [
          "Include",
          "Exclude"
        ],
        "description": "Whether an increased benefit applies for the first months after an accident-caused claim."
      },
      "IpBenefitPeriod": {
        "enum": [
          "Years1",
          "Years2",
          "Years5",
          "ToAge55",
          "ToAge60",
          "ToAge65",
          "ToAge67",
          "ToAge70"
        ],
        "description": "How long Income Protection keeps paying a claim once it starts: a fixed term, or to a named age."
      },
      "IpFeature": {
        "enum": [
          "Standard",
          "Intermediate",
          "BestAvailable"
        ],
        "description": "Selects which tier of a supplier's feature set to quote: `Standard`, `Intermediate`, or\n`BestAvailable`. Feature detail is per supplier - `POST /clients/{clientId}/research` returns\nthe actual matrix."
      },
      "IpIncreaseClaimBenefit": {
        "enum": [
          "Include",
          "Exclude"
        ],
        "description": "Whether the monthly benefit automatically increases the longer a claim runs."
      },
      "IpOwnership": {
        "enum": [
          "NonSuper",
          "Super",
          "SMSF",
          "SuperLink",
          "SMSFSuperLink"
        ],
        "description": "How Income Protection is held: `NonSuper`, `Super`, `SMSF`, or the legacy linked structures\n`SuperLink`/`SMSFSuperLink`, kept for clients requoting an old policy."
      },
      "IpPremiumStructure": {
        "enum": [
          "VariableAgeStepped",
          "Variable"
        ],
        "description": "Premium structure for Income Protection: `VariableAgeStepped` (rises with age) or `Variable` (stays level)."
      },
      "IpReplacementRatio": {
        "enum": [
          "Any",
          "GreaterThan75",
          "Between70And75",
          "Between60And69",
          "LessThan60"
        ],
        "description": "The maximum monthly benefit that will be insured, as a band of the client's income: `Any` (no\ncap requested), `GreaterThan75`, `Between70And75`, `Between60And69`, or `LessThan60`."
      },
      "IpWaitingPeriod": {
        "enum": [
          "Days14",
          "Days30",
          "Days60",
          "Days90",
          "Days180",
          "Year1",
          "Years2"
        ],
        "description": "How many days of disability before Income Protection starts paying a claim."
      },
      "Life": {
        "required": [
          "sumInsured"
        ],
        "type": "object",
        "properties": {
          "sumInsured": {
            "maximum": 2147483647,
            "minimum": 1,
            "type": "integer",
            "description": "The lump sum paid on a claim, in AUD.",
            "format": "int32"
          },
          "premiumStructure": {
            "default": "VariableAgeStepped",
            "$ref": "#/components/schemas/LumpSumPremiumStructure"
          },
          "ownership": {
            "default": "NonSuper",
            "$ref": "#/components/schemas/LifeOwnership"
          }
        },
        "description": "Life cover: pays a lump sum on death or terminal illness."
      },
      "LifeOwnership": {
        "enum": [
          "NonSuper",
          "Super",
          "SMSF"
        ],
        "description": "How the Life cover is held: `NonSuper` (ordinary, outside superannuation), `Super` (inside\nsuperannuation), or `SMSF` (inside a self-managed super fund). Changes the tax treatment."
      },
      "LumpSumPremiumStructure": {
        "enum": [
          "VariableAgeStepped",
          "VariableToAge65",
          "VariableToAge70"
        ],
        "description": "A per-cover trait, using current APRA/industry names: `VariableAgeStepped` (premiums rise with\nage) or the level structures `VariableToAge65`/`VariableToAge70` (fixed to the named age)."
      },
      "NeedleStick": {
        "required": [
          "sumInsured"
        ],
        "type": "object",
        "properties": {
          "sumInsured": {
            "maximum": 2147483647,
            "minimum": 1,
            "type": "integer",
            "description": "The lump sum paid on a claim, in AUD.",
            "format": "int32"
          }
        },
        "description": "Needle Stick: pays a lump sum on accidental exposure to blood or bodily fluid that results in\ninfection with a specified disease. No configurable options beyond sum insured."
      },
      "OAuthAuthorizationServerMetadata": {
        "required": [
          "issuer",
          "token_endpoint",
          "grant_types_supported",
          "token_endpoint_auth_methods_supported"
        ],
        "type": "object",
        "properties": {
          "issuer": {
            "type": "string",
            "description": "The authorization server's issuer identifier - the API's canonical resource URL."
          },
          "token_endpoint": {
            "type": "string",
            "description": "Absolute URL of the token endpoint (`{issuer}/api/auth/token`)."
          },
          "grant_types_supported": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The grant types supported - client-credentials only."
          },
          "token_endpoint_auth_methods_supported": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Client authentication methods at the token endpoint - `client_secret_post`."
          }
        },
        "description": "RFC 8414 authorization server metadata, served at\n`/.well-known/oauth-authorization-server`, so a client can discover the token endpoint and\nthe supported grant. Only the fields relevant to the client-credentials flow are present: there\nis no interactive authorization flow, so no authorization endpoint is published."
      },
      "OAuthErrorResponse": {
        "required": [
          "error"
        ],
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "The RFC 6749 / RFC 8707 error code."
          },
          "error_description": {
            "type": [
              "null",
              "string"
            ],
            "description": "Human-readable detail (optional per spec; included to aid debugging)."
          }
        },
        "description": "An OAuth2 error response in RFC 6749 §5.2 shape (snake_case), returned by the token endpoint:\n`invalid_client` (401), `unsupported_grant_type` (400), `invalid_target`\n(RFC 8707, 400), `invalid_request` (400)."
      },
      "OAuthProtectedResourceMetadata": {
        "required": [
          "resource",
          "authorization_servers"
        ],
        "type": "object",
        "properties": {
          "resource": {
            "type": "string",
            "description": "The protected resource's identifier - the API's canonical resource URL."
          },
          "authorization_servers": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The authorization servers that issue tokens for this resource (RFC 8414 issuers)."
          }
        },
        "description": "RFC 9728 protected resource metadata, served at `/.well-known/oauth-protected-resource`.\nEvery 401 points here through its `WWW-Authenticate: resource_metadata=…` challenge, which\nis how a client discovers where to obtain a token without prior configuration."
      },
      "OAuthTokenResponse": {
        "required": [
          "access_token",
          "expires_in"
        ],
        "type": "object",
        "properties": {
          "access_token": {
            "type": "string",
            "description": "The signed JWT access token."
          },
          "token_type": {
            "type": "string",
            "description": "Always `Bearer`."
          },
          "expires_in": {
            "type": "integer",
            "description": "Seconds until the token expires.",
            "format": "int32"
          }
        },
        "description": "A successfully issued access token in RFC 6749 §5.1 shape (snake_case), the sole response of the\nOAuth2 client-credentials token endpoint. Present it as\n`Authorization: Bearer {access_token}` on every other endpoint."
      },
      "OccupationResponse": {
        "required": [
          "id",
          "description"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The occupation id, for use as `occupationId` on a client."
          },
          "description": {
            "type": "string",
            "description": "The occupation's display description."
          }
        },
        "description": "One occupation, in a readable shape. The id's format is not normalised - it may be unpadded\n(e.g. `1`) rather than zero-padded."
      },
      "OwnershipAmount": {
        "type": "object",
        "properties": {
          "premium": {
            "type": "number",
            "description": "Premium excluding stamp duty, with rollover not assumed, in AUD.",
            "format": "double"
          },
          "stampDuty": {
            "type": "number",
            "description": "Stamp duty payable, in AUD.",
            "format": "double"
          },
          "totalPremium": {
            "type": "number",
            "description": "What is actually payable: `premium + stampDuty`, in AUD.",
            "format": "double"
          }
        },
        "description": "One side of an ownership split, using the same premium figures as the parent breakdown."
      },
      "OwnershipSplit": {
        "type": "object",
        "properties": {
          "insideSuper": {
            "$ref": "#/components/schemas/OwnershipAmount"
          },
          "outsideSuper": {
            "$ref": "#/components/schemas/OwnershipAmount"
          }
        },
        "description": "A premium split by where the cover is held: inside or outside superannuation."
      },
      "PagedResultOfBillingClientEntry": {
        "required": [
          "items",
          "totalCount"
        ],
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BillingClientEntry"
            },
            "description": "The items on this page, in stable listing order."
          },
          "totalCount": {
            "type": "integer",
            "description": "Total number of items across all pages (for the current filter), so callers can show\n\"X of N\" without walking every page.",
            "format": "int32"
          },
          "nextCursor": {
            "type": [
              "null",
              "string"
            ],
            "description": "Opaque token to pass as the `cursor` query parameter for the next page, or null when\nthere are no further pages. Do not parse or construct it - treat it as an opaque string."
          },
          "hasMore": {
            "type": "boolean",
            "description": "True when a further page exists (i.e. `nextCursor` is non-null)."
          }
        },
        "description": "A single page of a listing, in Simple's readable shape. Cursor-based (keyset) pagination:\ncallers pass `nextCursor` back as the `cursor` query parameter to fetch the following page.\nA null `nextCursor` means this is the last page."
      },
      "PagedResultOfClientResponse": {
        "required": [
          "items",
          "totalCount"
        ],
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ClientResponse"
            },
            "description": "The items on this page, in stable listing order."
          },
          "totalCount": {
            "type": "integer",
            "description": "Total number of items across all pages (for the current filter), so callers can show\n\"X of N\" without walking every page.",
            "format": "int32"
          },
          "nextCursor": {
            "type": [
              "null",
              "string"
            ],
            "description": "Opaque token to pass as the `cursor` query parameter for the next page, or null when\nthere are no further pages. Do not parse or construct it - treat it as an opaque string."
          },
          "hasMore": {
            "type": "boolean",
            "description": "True when a further page exists (i.e. `nextCursor` is non-null)."
          }
        },
        "description": "A single page of a listing, in Simple's readable shape. Cursor-based (keyset) pagination:\ncallers pass `nextCursor` back as the `cursor` query parameter to fetch the following page.\nA null `nextCursor` means this is the last page."
      },
      "PaymentFrequency": {
        "enum": [
          "Yearly",
          "Monthly"
        ],
        "description": "A scenario-level choice. Yearly is the default."
      },
      "PremiumBreakdown": {
        "type": "object",
        "properties": {
          "premium": {
            "type": "number",
            "description": "Premium excluding stamp duty, with rollover NOT assumed.",
            "format": "double"
          },
          "stampDuty": {
            "type": "number",
            "description": "Stamp duty payable. Added to `premium` to get `totalPremium`.",
            "format": "double"
          },
          "totalPremium": {
            "type": "number",
            "description": "What is actually payable: `Premium + StampDuty`.",
            "format": "double"
          },
          "possibleRolloverRebate": {
            "type": "number",
            "description": "Potential saving if the client is eligible for rollover. Rollover-discounted price =\n`TotalPremium - PossibleRolloverRebate`. Includes the stamp duty the discount also saves\n(duty is charged on the discounted premium), so it can exceed 15% of `premium`.\n\"Possible\" because eligibility depends on the client's super situation.",
            "format": "double"
          },
          "byOwnership": {
            "description": "The same figures split by ownership (inside vs outside super), also with rollover not\nassumed. A rollover saving only applies to cover held inside super, so the whole\n`possibleRolloverRebate` sits on the `insideSuper` side; `outsideSuper` never includes any of it.",
            "$ref": "#/components/schemas/OwnershipSplit"
          }
        },
        "description": "The single money shape reused across the whole quote - by each cover, the supplier's\n`premiumTotal`, and the `policyFee`. Designed so a caller or an AI agent learns it once.\n\nStamp duty is additive and never double-counted: `premium` + `stampDuty` = `totalPremium`.\nRollover is not assumed in these figures: `premium` is the price without rollover, and\n`possibleRolloverRebate` is the saving to subtract if the client turns out to be eligible."
      },
      "ProblemCode": {
        "enum": [
          "validation_failed",
          "client_not_found",
          "supplier_not_found",
          "billing_period_not_found",
          "route_not_found",
          "method_not_allowed",
          "unsupported_media_type",
          "identity_locked",
          "occupation_not_recognised",
          "omnilife_credentials_invalid",
          "rate_limit_exceeded",
          "omnilife_unavailable",
          "omnilife_response_invalid",
          "request_not_priceable",
          "internal_error"
        ],
        "type": "string",
        "description": "The stable, machine-readable identity of a failure - the member to branch on, since one status code covers several conditions. A future release may add a code, so treat an unrecognised one as the status code alone would be treated.\n\n- `validation_failed` (400) - The request failed validation. `errors` names each offending field; fix and resend.\n- `client_not_found` (404) - No client with that id is visible to the calling API user - it never existed, belongs to another API user, or has been deleted.\n- `supplier_not_found` (404) - No supplier with that code is available to the calling API user.\n- `billing_period_not_found` (404) - The requested month lies outside the API user's billing history.\n- `route_not_found` (404) - No endpoint exists at that path. Check the path against this document - every resource lives under `/api/v1`.\n- `method_not_allowed` (405) - The path exists but does not accept that HTTP method. The `Allow` header lists the methods it does accept.\n- `unsupported_media_type` (415) - The request body's content type is not one the endpoint accepts. `detail` names the accepted types; resend the body with one of them.\n- `identity_locked` (409) - The change would alter a locked identity field in a way that is not consistent with the same person. `fields` names them; create a new client instead.\n- `occupation_not_recognised` (422) - The client's `occupationId` is not a recognised occupation, so it cannot be priced. Search occupations for a current id, update the client, then retry. The request was not charged.\n- `omnilife_credentials_invalid` (401) - The access token is no longer valid for an active API user, or the pricing engine did not accept its account. Request a new token; if that fails, contact support.\n- `rate_limit_exceeded` (429) - The rate limit was exceeded. Wait for the `Retry-After` header's number of seconds, then retry.\n- `omnilife_unavailable` (502) - The pricing engine could not be reached, so no result was produced. The request was not charged and can be retried.\n- `omnilife_response_invalid` (502) - The pricing engine answered with something this API could not interpret. Retrying is unlikely to help; quote the correlation id to support.\n- `request_not_priceable` (422) - A value in the request or the stored client could not be accepted for pricing, and it could not be traced to a specific field. Retrying unchanged fails the same way; quote the correlation id to support. The request was not charged.\n- `internal_error` (500) - An unexpected error. Quote the correlation id to support."
      },
      "ProblemDetails": {
        "type": "object",
        "properties": {
          "type": {
            "type": [
              "null",
              "string"
            ],
            "description": "A URI identifying the kind of problem. Informational only - `code` is the member to branch on."
          },
          "title": {
            "type": [
              "null",
              "string"
            ],
            "description": "A short summary of the kind of problem, in English and the same for every occurrence of one `code`."
          },
          "status": {
            "type": [
              "null",
              "integer"
            ],
            "description": "The HTTP status code, repeated in the body.",
            "format": "int32"
          },
          "detail": {
            "type": [
              "null",
              "string"
            ],
            "description": "What went wrong on this particular request, written for a person. Not stable between releases and not meant to be parsed."
          },
          "instance": {
            "type": [
              "null",
              "string"
            ],
            "description": "The path of the request that failed."
          },
          "code": {
            "$ref": "#/components/schemas/ProblemCode"
          },
          "correlationId": {
            "type": [
              "null",
              "string"
            ],
            "description": "The request's correlation id, quoted to support to have this exact request traced in the server logs. Carried in the body on the failures where support is the next step rather than a change to the request. The same id is returned on the `X-Correlation-ID` response header of every response, so it can always be read from there instead; supplying that header on a request adopts the given id."
          },
          "fields": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "string"
            },
            "description": "Present only on an `identity_locked` conflict: the client fields whose change was refused."
          }
        },
        "description": "The shape every failed request returns (RFC 9457, `application/problem+json`). Branch on `code`: `status` and `title` are each shared by several conditions, and `detail` is prose written for a person, which may be reworded at any time."
      },
      "ProjectionResponse": {
        "type": "object",
        "properties": {
          "quotedAt": {
            "type": "string",
            "description": "When this result was computed, in UTC.",
            "format": "date-time"
          },
          "referenceId": {
            "type": [
              "null",
              "string"
            ],
            "description": "Caller-supplied correlation id, echoed back. Not persisted."
          },
          "currency": {
            "type": "string",
            "description": "Currency of every monetary amount in the response. Always \"AUD\"."
          },
          "paymentFrequency": {
            "description": "The frequency every premium figure in the response is expressed at.",
            "$ref": "#/components/schemas/PaymentFrequency"
          },
          "years": {
            "type": "integer",
            "description": "Number of years projected. Default 5, overridable on the request.",
            "format": "int32"
          },
          "indexationRate": {
            "type": "number",
            "description": "Annual indexation applied to the sum insured. Default 0.0 (0%), range 0.0-0.1.",
            "format": "double"
          },
          "quotedAge": {
            "type": "integer",
            "description": "The age this projection was actually priced at: exact for a client with a date of birth,\notherwise the estimated current age derived from the stored `age` and `ageAsAt` (the\ncapture-anniversary convention).",
            "format": "int32"
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SupplierProjectionResult"
            },
            "description": "One result per quoted supplier."
          }
        },
        "description": "Returned by `POST /clients/{clientId}/projections`. Same Scenario body\nas a quote, computed statelessly. Only the headline total is projected per year - no\nper-cover breakdown per year, since that would be too large and hard to consume. Holds one\nSupplierProjectionResult per quoted supplier."
      },
      "ProjectionYear": {
        "type": "object",
        "properties": {
          "year": {
            "type": "integer",
            "description": "Years from now; `0` is the current year.",
            "format": "int32"
          },
          "totalPremium": {
            "type": "number",
            "description": "The total premium for this year, in AUD.",
            "format": "double"
          }
        },
        "description": "One projected year's total premium."
      },
      "QuoteCovers": {
        "type": "object",
        "properties": {
          "policyFee": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "description": "Combined policy fee across all products, using the shared premium shape.",
                "$ref": "#/components/schemas/PremiumBreakdown"
              }
            ]
          },
          "life": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/CoverResult"
              }
            ]
          },
          "tpdExtensionToLife": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/CoverResult"
              }
            ]
          },
          "traumaExtensionToLife": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/CoverResult"
              }
            ]
          },
          "tpdStandalone": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/CoverResult"
              }
            ]
          },
          "traumaStandalone": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/CoverResult"
              }
            ]
          },
          "tpdExtensionToTrauma": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/CoverResult"
              }
            ]
          },
          "incomeProtection": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/CoverResult"
              }
            ]
          },
          "businessExpenses": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/CoverResult"
              }
            ]
          },
          "needleStick": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/CoverResult"
              }
            ]
          },
          "childTraumas": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/CoverResult"
            },
            "description": "The Child Trauma result. Every insured child on the scenario is priced together as one\ncombined cover, so this holds at most one entry rather than one per child. Null if Child\nTrauma was not requested."
          }
        },
        "description": "Per-cover results, flat and symmetric with the request: an extension cover (TPD Extension to\nLife, Trauma Extension to Life, TPD Extension to Trauma) is a top-level sibling rather than\nnested, with the same key as in the request. Each property is null if that cover was not\nrequested or not priced."
      },
      "QuoteError": {
        "type": "object",
        "properties": {
          "level": {
            "$ref": "#/components/schemas/QuoteErrorLevel"
          },
          "code": {
            "type": "string",
            "description": "Stable code where derivable: \"no_product_meets_need\", \"below_minimum_premium\",\n\"frequency_not_supported\", \"quote_generation_failed\"."
          },
          "cover": {
            "type": [
              "null",
              "string"
            ],
            "description": "The cover key (e.g. \"life\") for cover-level errors; null for supplier-level."
          },
          "message": {
            "type": "string",
            "description": "Human/AI-readable description."
          },
          "excludedProducts": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/ExcludedProduct"
            },
            "description": "Per-product exclusion reasons: why each candidate product could not meet the cover.\nFreeform prose - the text an AI agent needs to self-correct."
          },
          "detail": {
            "type": [
              "null",
              "object"
            ],
            "description": "Optional code-specific structured detail (e.g. premium/minimumPremium amounts)."
          }
        },
        "description": "A structured quote error. The stable `code` lets a caller or an AI agent branch without\nparsing prose; `excludedProducts` carries per-product exclusion reasons, which are the\nactionable hints for self-correction. A stable `code` is only assigned where it can be\nderived reliably; otherwise `code` falls back to `quote_generation_failed` and the free-text\n`message` carries the detail."
      },
      "QuoteErrorLevel": {
        "enum": [
          "Supplier",
          "Cover"
        ],
        "description": "The tier an error applies to. API-level errors are HTTP 400 and never appear here."
      },
      "QuoteOptions": {
        "type": "object",
        "properties": {
          "includePremiumWaiver": {
            "type": "boolean",
            "description": "Waives premiums while a claim is open, applied to every cover that supports it. A\nscenario-wide flag, since insurers price premium waiver per policy, not per cover.",
            "default": false
          },
          "commissionType": {
            "default": "Auto",
            "$ref": "#/components/schemas/CommissionType"
          },
          "paymentFrequency": {
            "default": "Yearly",
            "$ref": "#/components/schemas/PaymentFrequency"
          }
        },
        "description": "Scenario-level pricing choices: premium waiver, commission, and payment frequency. Optional:\nevery field has a default, and omitting `quoteOptions` altogether is the same as sending `{}` -\nno premium waiver, `Auto` commission, and `Yearly` premiums."
      },
      "QuoteProduct": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "description": "The product's code."
          },
          "name": {
            "type": "string",
            "description": "The product's display name."
          }
        },
        "description": "The product used to price a cover."
      },
      "QuoteResponse": {
        "type": "object",
        "properties": {
          "quotedAt": {
            "type": "string",
            "description": "When this result was computed, in UTC.",
            "format": "date-time"
          },
          "referenceId": {
            "type": [
              "null",
              "string"
            ],
            "description": "Caller-supplied correlation id, echoed back. Not persisted."
          },
          "currency": {
            "type": "string",
            "description": "Currency of every monetary amount in the response. Always \"AUD\"."
          },
          "paymentFrequency": {
            "description": "The frequency every premium figure in the response is expressed at.",
            "$ref": "#/components/schemas/PaymentFrequency"
          },
          "quotedAge": {
            "type": "integer",
            "description": "The age this result was actually priced at: exact for a client with a date of birth,\notherwise the estimated current age derived from the stored `age` and `ageAsAt` (the\ncapture-anniversary convention). Named here so a client priced at a silently different age\nthan the one the caller stored is never a surprise.",
            "format": "int32"
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SupplierQuoteResult"
            },
            "description": "One result per quoted supplier."
          }
        },
        "description": "Returned by `POST /clients/{clientId}/quotes`. Quotes are\nstateless: computed fresh on every call and never stored, so there is no quoteId,\nno stored result, and no staleness. Holds one SupplierQuoteResult per quoted supplier."
      },
      "ResearchResponse": {
        "type": "object",
        "properties": {
          "quotedAt": {
            "type": "string",
            "description": "When this result was computed, in UTC.",
            "format": "date-time"
          },
          "referenceId": {
            "type": [
              "null",
              "string"
            ],
            "description": "Caller-supplied correlation id, echoed back. Not persisted."
          },
          "suppliers": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The supplier codes that met every requested cover - the matrix columns. A supplier result\ncan meet only some of the requested covers, but research never shows a partial result: a\nsupplier that couldn't meet every cover is listed in `suppliersWithUnmetNeeds` instead and\nnever appears in `covers`."
          },
          "suppliersWithUnmetNeeds": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The supplier codes whose result could not meet every requested cover. These are excluded\nfrom the `covers` feature matrix entirely - showing their features would read as\n\"product exists but lacks these features\", when in fact no product met every cover at all."
          },
          "covers": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/CoverFeatures"
            },
            "description": "Feature matrix grouped by cover. Key = cover name (e.g. \"life\"); one entry per requested\ncover (empty when that cover has no simple features). Empty overall when no supplier met all\nneeds - a matrix of nothing but excluded suppliers would be noise."
          }
        },
        "description": "Returned by `POST /clients/{clientId}/research`. Same Scenario body as\na quote/projection, computed statelessly. A simple feature matrix: per requested\ncover, which suppliers support each feature. No score - the single portfolio score is on the\npremium response."
      },
      "Retention": {
        "type": "object",
        "properties": {
          "timeToLiveDays": {
            "type": [
              "null",
              "integer"
            ],
            "description": "Days until expiry. Null = never expire. Default ~3 years.",
            "format": "int32",
            "default": 1095
          },
          "isSlidingExpiration": {
            "type": "boolean",
            "description": "When true, any billable activity (quote/projection/research) resets the clock.",
            "default": true
          }
        },
        "description": "Controls how long the client's personally identifiable information (PII) is retained."
      },
      "Scenario": {
        "required": [
          "covers"
        ],
        "type": "object",
        "properties": {
          "referenceId": {
            "type": [
              "null",
              "string"
            ],
            "description": "Optional caller-supplied correlation id, echoed back verbatim on the response.\nNever persisted or interpreted by Simple."
          },
          "includedSuppliers": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "string"
            },
            "description": "Optional list of supplier codes to quote. Replaces the\ndefault Retail supplier filter entirely when present; absent means all Retail suppliers."
          },
          "quoteOptions": {
            "$ref": "#/components/schemas/QuoteOptions"
          },
          "supplierOverrides": {
            "type": [
              "null",
              "object"
            ],
            "additionalProperties": {
              "$ref": "#/components/schemas/SupplierOverride"
            },
            "description": "Optional per-supplier forced product codes, keyed by supplier code."
          },
          "covers": {
            "$ref": "#/components/schemas/ScenarioCovers"
          }
        },
        "description": "The replayable input to every pricing operation for one Client: cover\nconfiguration, quote options, and supplier selection. The same body is posted to\n`/clients/{clientId}/quotes`, `/projections`, and `/research`. Stateless - never\npersisted."
      },
      "ScenarioCovers": {
        "minProperties": 1,
        "type": "object",
        "properties": {
          "life": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/Life"
              }
            ]
          },
          "tpdExtensionToLife": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/TPDExtensionToLife"
              }
            ]
          },
          "traumaExtensionToLife": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/TraumaExtensionToLife"
              }
            ]
          },
          "tpdStandalone": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/TPDStandalone"
              }
            ]
          },
          "traumaStandalone": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/TraumaStandalone"
              }
            ]
          },
          "tpdExtensionToTrauma": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/TPDExtensionToTrauma"
              }
            ]
          },
          "incomeProtection": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/IncomeProtection"
              }
            ]
          },
          "businessExpenses": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/BusinessExpenses"
              }
            ]
          },
          "needleStick": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/NeedleStick"
              }
            ]
          },
          "childTraumas": {
            "maxItems": 9,
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/ChildTrauma"
            },
            "description": "Up to 9 insured children, priced together as one Child Trauma cover."
          }
        },
        "description": "The flat set of covers making up a scenario. An extension cover (TPD Extension to Life,\nTrauma Extension to Life, TPD Extension to Trauma) is a top-level sibling rather than nested\nunder the cover it extends; validation (not shape) enforces that its parent cover is also\npresent. At least one cover must be supplied."
      },
      "SmokerStatus": {
        "enum": [
          "Smoker",
          "NonSmoker"
        ],
        "description": "Whether the client is a smoker, used in premium rating."
      },
      "SupplierDocuments": {
        "type": "object",
        "properties": {
          "pds": {
            "type": [
              "null",
              "string"
            ],
            "description": "The Primary Product Disclosure Statement (PDS + SPDS combined)."
          },
          "tmd": {
            "type": [
              "null",
              "string"
            ],
            "description": "The Target Market Determination."
          }
        },
        "description": "Current supplier documents - the Product Disclosure Statement (PDS, combined with any\nSupplementary PDS/SPDS) and Target Market Determination (TMD). Values are absolute URLs; null\nwhen not published."
      },
      "SupplierLogo": {
        "type": "object",
        "properties": {
          "svg": {
            "type": [
              "null",
              "string"
            ],
            "description": "URL to the supplier's SVG logo."
          }
        },
        "description": "Supplier logo links."
      },
      "SupplierOverride": {
        "type": "object",
        "properties": {
          "products": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/SupplierProductCodes"
              }
            ]
          }
        },
        "description": "A per-supplier override block, products only. Occupation\noverrides live on the Client instead, and commission is scenario-level rather than per-supplier."
      },
      "SupplierProduct": {
        "required": [
          "productName",
          "productCode"
        ],
        "type": "object",
        "properties": {
          "productName": {
            "type": "string",
            "description": "The product's display name."
          },
          "productCode": {
            "type": "string",
            "description": "The product's code, usable in a supplier override to force this product."
          },
          "supportedCovers": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/SupportedCover"
            },
            "description": "The covers this product supports, keyed by cover name (e.g. `life`,\n`tpdExtensionToLife`)."
          }
        },
        "description": "A product offered by a supplier, with the covers it supports."
      },
      "SupplierProductCodes": {
        "type": "object",
        "properties": {
          "life": {
            "type": [
              "null",
              "string"
            ],
            "description": "Forces this supplier's product for the Life cover, in place of its auto-resolved default."
          },
          "tpdStandalone": {
            "type": [
              "null",
              "string"
            ],
            "description": "Forces this supplier's product for TPD Standalone, in place of its auto-resolved default."
          },
          "traumaStandalone": {
            "type": [
              "null",
              "string"
            ],
            "description": "Forces this supplier's product for Trauma Standalone, in place of its auto-resolved default."
          },
          "incomeProtection": {
            "type": [
              "null",
              "string"
            ],
            "description": "Forces this supplier's product for Income Protection, in place of its auto-resolved default."
          },
          "businessExpenses": {
            "type": [
              "null",
              "string"
            ],
            "description": "Forces this supplier's product for Business Expenses, in place of its auto-resolved default."
          },
          "needleStick": {
            "type": [
              "null",
              "string"
            ],
            "description": "Forces this supplier's product for Needle Stick, in place of its auto-resolved default."
          },
          "childTrauma": {
            "type": [
              "null",
              "string"
            ],
            "description": "Not supported: Child Trauma has no per-supplier product code to force. Setting this field, for\nany supplier, fails the call with a `400` `validation_failed` naming it, whether or not the\nscenario includes `childTraumas` - it is never silently ignored."
          }
        },
        "description": "Per-supplier forced product codes."
      },
      "SupplierProjectionResult": {
        "type": "object",
        "properties": {
          "supplierCode": {
            "type": "string",
            "description": "The supplier's code, as returned by `GET /suppliers`."
          },
          "supplierName": {
            "type": "string",
            "description": "The supplier's display name."
          },
          "allNeedsMet": {
            "type": "boolean",
            "description": "True only if every requested cover was successfully priced by this supplier."
          },
          "projection": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProjectionYear"
            },
            "description": "Total premium per year, year 0 = the current year."
          },
          "coverEnds": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CoverEnd"
            },
            "description": "Which covers stop within the projection window, and when - explains a future premium drop.\nA bare list only: no reason for the change is stated."
          },
          "errors": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/QuoteError"
            },
            "description": "Structured supplier-level and cover-level errors. See QuoteError."
          }
        },
        "description": "Projection result for one supplier. Follows the same \"no partials\" rule as a quote: when a\nsupplier cannot price every requested cover it returns no projection at all, `allNeedsMet` is\n`false`, and `errors` is populated instead."
      },
      "SupplierQuoteResult": {
        "type": "object",
        "properties": {
          "supplierCode": {
            "type": "string",
            "description": "The supplier's code, as returned by `GET /suppliers`."
          },
          "supplierName": {
            "type": "string",
            "description": "The supplier's display name."
          },
          "allNeedsMet": {
            "type": "boolean",
            "description": "True only if every requested cover was successfully priced by this supplier."
          },
          "featureScore": {
            "type": [
              "null",
              "number"
            ],
            "description": "How well the features of the products quoted for this scenario rate, from 0 to 100, under\nOmnium's product-research methodology. Omnium researches each insurer's product features and\nscores them the same way for every insurer. Higher means a stronger feature set; the score\nsays nothing about price. Compare it across suppliers in the same quote only, since the score\ndepends on the covers and options requested. It is not advice or a recommendation. Null if\nthe supplier could not be priced.",
            "format": "double"
          },
          "occupation": {
            "type": [
              "null",
              "string"
            ],
            "description": "Resolved supplier occupation description (e.g. \"Doctor - General Practitioner\")."
          },
          "commission": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "description": "The adviser commission this supplier pays on the quote, if any was resolved.",
                "$ref": "#/components/schemas/CommissionResult"
              }
            ]
          },
          "premiumTotal": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "description": "Supplier-total premium, using the shared PremiumBreakdown shape.",
                "$ref": "#/components/schemas/PremiumBreakdown"
              }
            ]
          },
          "covers": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/QuoteCovers"
              }
            ]
          },
          "errors": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/QuoteError"
            },
            "description": "Structured supplier-level and cover-level errors. See QuoteError."
          }
        },
        "description": "Premium result for one supplier within a quote. When a supplier cannot\nprice every requested cover it returns NO premium (no partials): `allNeedsMet` is `false`,\n`premiumTotal`/`covers`/`commission`/`featureScore` are `null`, and `errors` is populated. A\nfailing supplier never fails the other suppliers in the same quote."
      },
      "SupplierResponse": {
        "required": [
          "supplierCode",
          "supplierName",
          "supplierType",
          "isRetail"
        ],
        "type": "object",
        "properties": {
          "supplierCode": {
            "type": "string",
            "description": "The supplier code (e.g. \"ACC\")."
          },
          "supplierName": {
            "type": "string",
            "description": "The supplier's display name (e.g. \"TAL\")."
          },
          "supplierType": {
            "type": "string",
            "description": "Readable supplier type, e.g. `Retail` or `IndustrySuper`."
          },
          "isRetail": {
            "type": "boolean",
            "description": "True when `supplierType` is `Retail` - the type a scenario's default supplier selection\nis built from when `includedSuppliers` is omitted. Prefer this over comparing\n`supplierType` to a string literal - it keeps working if the exact type label ever\nchanges."
          },
          "documents": {
            "description": "Current supplier documents, keyed by readable type.",
            "$ref": "#/components/schemas/SupplierDocuments"
          },
          "logo": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "description": "Supplier logo links. Null when the supplier has none on file.",
                "$ref": "#/components/schemas/SupplierLogo"
              }
            ]
          },
          "products": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SupplierProduct"
            },
            "description": "The products this supplier offers."
          }
        },
        "description": "A supplier available to the calling API user, in Simple's readable shape. Bundles the supplier's\nproducts, current PDS/TMD documents, and SVG logo so a consumer needs one round-trip.\nReference data is per-credential, not global."
      },
      "SupportedCover": {
        "type": "object",
        "properties": {
          "ownership": {
            "type": [
              "null",
              "string"
            ],
            "description": "Readable ownership the product applies to this cover (`NonSuper`, `Super`, `SMSF`,\n`SuperLink`, `SMSFSuperLink`). Null when unspecified."
          },
          "optional": {
            "type": "boolean",
            "description": "True when the cover is optional on the product; false when mandatory."
          }
        },
        "description": "How a product supports one cover."
      },
      "TPDExtensionOwnership": {
        "enum": [
          "NonSuper",
          "Super",
          "SMSF",
          "SuperLink"
        ],
        "description": "How Total and Permanent Disability (TPD) Extension to Life is held: `NonSuper`, `Super`,\n`SMSF`, or `SuperLink` (a legacy super-linked structure some suppliers still support, kept for\nclients requoting an old policy)."
      },
      "TPDExtensionToLife": {
        "required": [
          "sumInsured"
        ],
        "type": "object",
        "properties": {
          "sumInsured": {
            "maximum": 2147483647,
            "minimum": 1,
            "type": "integer",
            "description": "The additional lump sum paid on a TPD claim, on top of `life`, in AUD.",
            "format": "int32"
          },
          "premiumStructure": {
            "default": "VariableAgeStepped",
            "$ref": "#/components/schemas/LumpSumPremiumStructure"
          },
          "ownership": {
            "default": "NonSuper",
            "$ref": "#/components/schemas/TPDExtensionOwnership"
          },
          "lifeBuyBack": {
            "default": "Exclude",
            "$ref": "#/components/schemas/TPDLifeBuyBack"
          },
          "doubleTPD": {
            "default": "Exclude",
            "$ref": "#/components/schemas/DoubleTPD"
          },
          "occupationType": {
            "default": "Any",
            "$ref": "#/components/schemas/TPDOccupationType"
          }
        },
        "description": "TPD Extension to Life: pays an additional lump sum on top of `life` if the client becomes\ntotally and permanently disabled (TPD). Requires `life`."
      },
      "TPDExtensionToTrauma": {
        "required": [
          "sumInsured"
        ],
        "type": "object",
        "properties": {
          "sumInsured": {
            "maximum": 2147483647,
            "minimum": 1,
            "type": "integer",
            "description": "The additional lump sum paid on a TPD claim, on top of `traumaStandalone`, in AUD.",
            "format": "int32"
          },
          "ownership": {
            "default": "NonSuper",
            "$ref": "#/components/schemas/TPDOwnership"
          },
          "premiumStructure": {
            "default": "VariableAgeStepped",
            "$ref": "#/components/schemas/LumpSumPremiumStructure"
          },
          "occupationType": {
            "default": "Any",
            "$ref": "#/components/schemas/TPDOccupationType"
          }
        },
        "description": "TPD Extension to Trauma: pays an additional lump sum on top of `traumaStandalone` if the\nclient becomes totally and permanently disabled (TPD). Requires `traumaStandalone`."
      },
      "TPDLifeBuyBack": {
        "enum": [
          "Immediate",
          "OneYear",
          "Exclude"
        ],
        "description": "Whether the TPD sum insured is restored after it reduces the linked Life cover, and how soon."
      },
      "TPDOccupationType": {
        "enum": [
          "Own",
          "Any",
          "ADL",
          "Homemaker"
        ],
        "description": "How disability is assessed for a TPD claim: `Own` occupation, `Any` occupation, `ADL`\n(activities of daily living, for clients not in paid work), or `Homemaker`."
      },
      "TPDOwnership": {
        "enum": [
          "NonSuper",
          "Super",
          "SMSF",
          "SuperLink",
          "SMSFSuperLink"
        ],
        "description": "How a TPD cover is held: `NonSuper`, `Super`, `SMSF`, or the legacy linked structures\n`SuperLink`/`SMSFSuperLink`, kept for clients requoting an old policy."
      },
      "TPDStandalone": {
        "required": [
          "sumInsured"
        ],
        "type": "object",
        "properties": {
          "sumInsured": {
            "maximum": 2147483647,
            "minimum": 1,
            "type": "integer",
            "description": "The lump sum paid on a TPD claim, in AUD.",
            "format": "int32"
          },
          "ownership": {
            "default": "NonSuper",
            "$ref": "#/components/schemas/TPDOwnership"
          },
          "premiumStructure": {
            "default": "VariableAgeStepped",
            "$ref": "#/components/schemas/LumpSumPremiumStructure"
          },
          "occupationType": {
            "default": "Any",
            "$ref": "#/components/schemas/TPDOccupationType"
          }
        },
        "description": "TPD Standalone: pays a lump sum if the client becomes totally and permanently disabled (TPD),\non its own rather than as an addition to another cover."
      },
      "TraumaExtensionToLife": {
        "required": [
          "sumInsured"
        ],
        "type": "object",
        "properties": {
          "sumInsured": {
            "maximum": 2147483647,
            "minimum": 1,
            "type": "integer",
            "description": "The additional lump sum paid on a trauma claim, on top of `life`, in AUD.",
            "format": "int32"
          },
          "premiumStructure": {
            "default": "VariableAgeStepped",
            "$ref": "#/components/schemas/LumpSumPremiumStructure"
          },
          "traumaLifeBuyBack": {
            "default": "Exclude",
            "$ref": "#/components/schemas/TraumaLifeBuyBack"
          },
          "doubleTrauma": {
            "default": "Exclude",
            "$ref": "#/components/schemas/DoubleTrauma"
          },
          "traumaReinstatement": {
            "default": "Exclude",
            "$ref": "#/components/schemas/TraumaReinstatement"
          },
          "babyCare": {
            "default": "Exclude",
            "$ref": "#/components/schemas/BabyCare"
          },
          "features": {
            "default": "Standard",
            "$ref": "#/components/schemas/TraumaFeature"
          }
        },
        "description": "Trauma Extension to Life: pays an additional lump sum on top of `life` on diagnosis of a\nspecified critical illness or injury (trauma). Requires `life`."
      },
      "TraumaFeature": {
        "enum": [
          "Standard",
          "Intermediate",
          "BestAvailable"
        ],
        "description": "Selects which tier of a supplier's feature set to quote: `Standard`, `Intermediate`, or\n`BestAvailable`. Feature detail is per supplier - `POST /clients/{clientId}/research` returns\nthe actual matrix."
      },
      "TraumaLifeBuyBack": {
        "enum": [
          "OneYear",
          "ThreeYears",
          "Exclude"
        ],
        "description": "Whether the trauma sum insured is restored after it reduces the linked Life cover, and how soon."
      },
      "TraumaReinstatement": {
        "enum": [
          "Include",
          "Exclude"
        ],
        "description": "Restores the trauma sum insured after a claim, so the cover continues. Increases the premium."
      },
      "TraumaStandalone": {
        "required": [
          "sumInsured"
        ],
        "type": "object",
        "properties": {
          "sumInsured": {
            "maximum": 2147483647,
            "minimum": 1,
            "type": "integer",
            "description": "The lump sum paid on a trauma claim, in AUD.",
            "format": "int32"
          },
          "premiumStructure": {
            "default": "VariableAgeStepped",
            "$ref": "#/components/schemas/LumpSumPremiumStructure"
          },
          "traumaReinstatement": {
            "default": "Exclude",
            "$ref": "#/components/schemas/TraumaReinstatement"
          },
          "babyCare": {
            "default": "Exclude",
            "$ref": "#/components/schemas/BabyCare"
          },
          "features": {
            "default": "Standard",
            "$ref": "#/components/schemas/TraumaFeature"
          }
        },
        "description": "Trauma Standalone: pays a lump sum on diagnosis of a specified critical illness or injury\n(trauma), on its own rather than as an addition to another cover."
      }
    },
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT"
      }
    }
  },
  "security": [
    {
      "bearerAuth": [ ]
    }
  ],
  "tags": [
    {
      "name": "Auth",
      "description": "Obtaining and scoping the bearer token every other endpoint requires, plus the two discovery documents that describe how to do so. Tokens come from the OAuth2 client-credentials grant, last an hour, and are not refreshable; cache one and reuse it, because this is the most tightly rate-limited part of the API."
    },
    {
      "name": "Suppliers",
      "description": "Reference data about the insurers available to the calling API user and the products each one offers, including the current product disclosure and target market documents. Supplier and product codes from here are what a scenario names when it asks for a specific insurer or product; the set is specific to the caller's entitlements, so it is read rather than assumed. Commission rates are commercially sensitive and are not published here - they appear only on a quote result, once a specific client and cover are priced."
    },
    {
      "name": "Occupations",
      "description": "Reference data for the occupation a client is quoted on, which materially affects price and eligibility. Search the shared catalogue by text, take a coarse generic category where an exact occupation is not known, or list one insurer's own occupations when a client needs a different occupation quoted with that insurer."
    },
    {
      "name": "Clients",
      "description": "The insured people the calling API user has created - the person a quote is about, never the business calling the API. A client is created once and then referenced by id, so the details that drive pricing are stated in one place rather than repeated on every quote. Identity fields lock shortly after creation so that one record cannot quietly become a different person, and each client carries its own retention period, after which personal details are erased."
    },
    {
      "name": "Quoting",
      "description": "Pricing a scenario for one client: premiums today, premiums projected over future years, and which product features each insurer supports. All three take the same scenario body and none of them stores anything - there is no quote id, and the same body re-prices from scratch every time. A successful call is a billable event for that client, which starts a 60-day period during which further calls for the same client are not charged again."
    },
    {
      "name": "Billing",
      "description": "What the calling API user has been charged for, by month and by client: how many distinct clients were billable, how many fell inside a 60-day exempt period, and which clients made up each figure. Indicative rather than an invoice - the agreement in force sets the actual terms."
    }
  ]
}