diff --git a/scripts/lib/openapi-docs.mjs b/scripts/lib/openapi-docs.mjs
index 7ac3046..a8bc19a 100644
--- a/scripts/lib/openapi-docs.mjs
+++ b/scripts/lib/openapi-docs.mjs
@@ -221,8 +221,22 @@ function createRenderer(spec, { baseUrl, tokenPlaceholder, modelsHref }) {
const resp = rawResp.$ref ? resolveRef(rawResp.$ref) : rawResp;
const media = jsonBody(resp);
- // An example authored on the response wins outright — it shows the whole
- // envelope, including any `meta`, exactly as the API returns it.
+ // Examples authored on the response win outright — they show the whole
+ // envelope, including any `meta`, exactly as the API returns it. Where a
+ // spec authors several, each is one the others cannot stand in for: a
+ // configured resource beside its unset default, a full page beside an
+ // empty one. They render in spec order, since `default` is only a key
+ // here and does not mean "the representative one".
+ const named = media?.examples;
+ if (named && typeof named === "object") {
+ const authoredAll = Object.entries(named).filter(
+ ([, entry]) => entry && "value" in entry
+ );
+ if (authoredAll.length > 1) {
+ return exampleBlocks(successCode, authoredAll);
+ }
+ }
+
const authored = mediaTypeExample(media);
if (authored !== undefined) {
return exampleBlock(successCode, authored);
@@ -266,6 +280,19 @@ function createRenderer(spec, { baseUrl, tokenPlaceholder, modelsHref }) {
return `\n**Example response \`${code}\`**\n\n\`\`\`json\n${JSON.stringify(body, null, 2)}\n\`\`\`\n`;
}
+ // Several authored examples under one heading, each captioned with its
+ // summary so the reader can see which case it is.
+ function exampleBlocks(code, entries) {
+ const blocks = entries
+ .map(([key, entry]) => {
+ const label = escapeMarkdown(entry.summary ?? key);
+ const body = JSON.stringify(entry.value, null, 2);
+ return `\n_${label}_\n\n\`\`\`json\n${body}\n\`\`\`\n`;
+ })
+ .join("");
+ return `\n**Example responses \`${code}\`**\n${blocks}`;
+ }
+
// -------------------------------------------------------------------------
// Types and constraints
// -------------------------------------------------------------------------
diff --git a/site/dolt/src/content/products/hosted/api/v1/README.md b/site/dolt/src/content/products/hosted/api/v1/README.md
index 12fbaba..e6de340 100644
--- a/site/dolt/src/content/products/hosted/api/v1/README.md
+++ b/site/dolt/src/content/products/hosted/api/v1/README.md
@@ -53,6 +53,7 @@ See [Authentication](/products/hosted/api/v1/authentication) for how to create a
| **PATCH** | `/api/v1/deployments/{owner}/{deployment}/config` | [Change some of a deployment's configuration overrides](/products/hosted/api/v1/deployment#patchDeploymentConfig) |
| **GET** | `/api/v1/deployments/{owner}/{deployment}/logs` | [Read a deployment's logs](/products/hosted/api/v1/deployment#getDeploymentLogs) |
| **PATCH** | `/api/v1/deployments/{owner}/{deployment}/expose` | [Expose or stop exposing the remotesapi or MCP endpoint](/products/hosted/api/v1/deployment#exposeDeploymentService) |
+| **GET** | `/api/v1/deployments/{owner}/{deployment}/service-windows` | [List a deployment's service windows](/products/hosted/api/v1/deployment#listDeploymentServiceWindows) |
| **GET** | `/api/v1/deployments/{owner}/{deployment}/metrics` | [List a deployment's metrics](/products/hosted/api/v1/deployment#listDeploymentMetrics) |
| **GET** | `/api/v1/deployments/{owner}/{deployment}/metrics/{metric}` | [Read one of a deployment's metrics](/products/hosted/api/v1/deployment#getDeploymentMetric) |
| **GET** | `/api/v1/deployments/{owner}/{deployment}/backups` | [List a deployment's backups](/products/hosted/api/v1/deployment#listDeploymentBackups) |
@@ -88,7 +89,7 @@ List endpoints put the pagination cursor in `meta`:
When `meta.next_page_token` is present, pass it back as the `page_token` query parameter to fetch the next page. On the last page `meta` is omitted entirely, so checking whether the token is present is all a client needs — it is never returned present but empty. Page size is fixed and not caller-controlled, so a full page is not itself a sign that another one follows.
-Two kinds of list depart from that. A pull request's [comments](/products/hosted/api/v1/pull-request#listDeploymentPullComments) and its [activity log](/products/hosted/api/v1/pull-request#listDeploymentPullLogs), and a deployment's [metrics catalogue](/products/hosted/api/v1/deployment#listDeploymentMetrics), are small enough by nature to be returned whole, so they take no `page_token` at all. And [log retrieval](/products/hosted/api/v1/deployment#getDeploymentLogs) walks a window of history rather than a finite list: it is the only endpoint that pages in both directions, and the only one whose page size you set (`lines`). There `meta.next_page_token` reads further back, `meta.prev_page_token` reads toward the present, both can be present at once, and either can come back on a page with no lines — so stop when a page comes back empty, not when a token is missing.
+Two kinds of list depart from that. A pull request's [comments](/products/hosted/api/v1/pull-request#listDeploymentPullComments) and its [activity log](/products/hosted/api/v1/pull-request#listDeploymentPullLogs), and a deployment's [metrics catalogue](/products/hosted/api/v1/deployment#listDeploymentMetrics) and [service windows](/products/hosted/api/v1/deployment#listDeploymentServiceWindows), are small enough by nature to be returned whole, so they take no `page_token` at all. And [log retrieval](/products/hosted/api/v1/deployment#getDeploymentLogs) walks a window of history rather than a finite list: it is the only endpoint that pages in both directions, and the only one whose page size you set (`lines`). There `meta.next_page_token` reads further back, `meta.prev_page_token` reads toward the present, both can be present at once, and either can come back on a page with no lines — so stop when a page comes back empty, not when a token is missing.
Each endpoint's parameters say which of the three it is.
diff --git a/site/dolt/src/content/products/hosted/api/v1/deployment.md b/site/dolt/src/content/products/hosted/api/v1/deployment.md
index af847b1..7bb98de 100644
--- a/site/dolt/src/content/products/hosted/api/v1/deployment.md
+++ b/site/dolt/src/content/products/hosted/api/v1/deployment.md
@@ -753,7 +753,9 @@ curl -X GET 'https://hosted.doltdb.com/api/v1/deployments/{owner}/{deployment}/l
| `500` | An unexpected server error occurred. | [`Problem`](/products/hosted/api/v1/models#model-problem) |
| `503` | The service is temporarily unavailable. | [`Problem`](/products/hosted/api/v1/models#model-problem) |
-**Example response `200`**
+**Example responses `200`**
+
+_Two lines with more history available._
```json
{
@@ -773,6 +775,14 @@ curl -X GET 'https://hosted.doltdb.com/api/v1/deployments/{owner}/{deployment}/l
}
```
+_A window with nothing in it._
+
+```json
+{
+ "data": []
+}
+```
+
---
## Expose or stop exposing a deployment's remotesapi or MCP endpoint {#exposeDeploymentService}
@@ -850,6 +860,80 @@ _Stop serving the remotesapi endpoint._
---
+## List a deployment's service windows {#listDeploymentServiceWindows}
+GET /api/v1/deployments/{owner}/{deployment}/service-windows
+
+Returns the weekly windows in which Hosted may restart the deployment's instances to apply maintenance.
+
+A window covers whole hours in UTC on one day of the week. `start_hour_utc` is inclusive and `end_hour_utc` is exclusive, so 7 and 8 mean the hour beginning 07:00 UTC.
+
+Every deployment has at least one. Until one is set, the list holds a single window with `is_default` set: Sunday 07:00 to 08:00 UTC.
+
+
+**Parameters**
+
+| Name | In | Type | Required | Description |
+|------|----|------|----------|-------------|
+| `owner` | path | string | yes | The user or organization that owns the deployment. 3–32 characters of letters, digits, hyphens, and underscores. |
+| `deployment` | path | string | yes | The deployment name, unique within the owner. 3–32 characters of letters, digits, hyphens, and underscores. |
+
+**Example request**
+
+```sh
+curl -X GET 'https://hosted.doltdb.com/api/v1/deployments/{owner}/{deployment}/service-windows' \
+ -H 'Authorization: Bearer YOUR_TOKEN'
+```
+
+**Responses**
+
+| Status | Description | Schema |
+|--------|-------------|--------|
+| `200` | The deployment's service windows. | [`ServiceWindow[]`](/products/hosted/api/v1/models#model-servicewindow) |
+| `400` | The request was malformed or failed input validation. | [`Problem`](/products/hosted/api/v1/models#model-problem) |
+| `401` | Authentication credentials were missing or invalid. | [`Problem`](/products/hosted/api/v1/models#model-problem) |
+| `403` | Authenticated, but not permitted to perform this action. | [`Problem`](/products/hosted/api/v1/models#model-problem) |
+| `404` | The requested resource does not exist. | [`Problem`](/products/hosted/api/v1/models#model-problem) |
+| `405` | The HTTP method is not supported for this resource. | [`Problem`](/products/hosted/api/v1/models#model-problem) |
+| `422` | The request was well-formed but semantically invalid. | [`Problem`](/products/hosted/api/v1/models#model-problem) |
+| `500` | An unexpected server error occurred. | [`Problem`](/products/hosted/api/v1/models#model-problem) |
+| `503` | The service is temporarily unavailable. | [`Problem`](/products/hosted/api/v1/models#model-problem) |
+
+**Example responses `200`**
+
+_A window set for early Tuesday morning UTC._
+
+```json
+{
+ "data": [
+ {
+ "id": "7c1e9a3b-2d4f-4a6c-8b0d-1e2f3a4b5c6d",
+ "day_of_week": "tuesday",
+ "start_hour_utc": 3,
+ "end_hour_utc": 5,
+ "is_default": false
+ }
+ ]
+}
+```
+
+_A deployment with no window configured._
+
+```json
+{
+ "data": [
+ {
+ "id": "00000000-0000-0000-0000-000000000000",
+ "day_of_week": "sunday",
+ "start_hour_utc": 7,
+ "end_hour_utc": 8,
+ "is_default": true
+ }
+ ]
+}
+```
+
+---
+
## Disable a deployment {#disableDeployment}
POST /api/v1/deployments/{owner}/{deployment}/disable
diff --git a/site/dolt/src/content/products/hosted/api/v1/models.md b/site/dolt/src/content/products/hosted/api/v1/models.md
index 8b547e2..186ecb5 100644
--- a/site/dolt/src/content/products/hosted/api/v1/models.md
+++ b/site/dolt/src/content/products/hosted/api/v1/models.md
@@ -251,6 +251,36 @@ One metric a deployment collects. Read it with `GET /api/v1/deployments/{owner}/
---
+## DayOfWeek {#model-dayofweek}
+The day of the week a service window falls on, in UTC.
+
+**Enum values**
+
+| Value |
+|-------|
+| `sunday` |
+| `monday` |
+| `tuesday` |
+| `wednesday` |
+| `thursday` |
+| `friday` |
+| `saturday` |
+
+---
+
+## ServiceWindow {#model-servicewindow}
+One weekly window in which Hosted may restart the deployment's instances for maintenance.
+
+| Field | Type | Required | Description |
+|-------|------|----------|-------------|
+| `id` | `string` | yes | The window's identifier, unique within the deployment. A default window has no identifier of its own and reports the nil UUID, `00000000-0000-0000-0000-000000000000`. |
+| `day_of_week` | [`DayOfWeek`](/products/hosted/api/v1/models#model-dayofweek) | yes | The day of the week a service window falls on, in UTC. |
+| `start_hour_utc` | `integer` | yes | The first hour of the window, in UTC. Inclusive. |
+| `end_hour_utc` | `integer` | yes | The hour the window ends, in UTC. Exclusive, so a window of 3 to 5 covers 03:00 until 05:00. |
+| `is_default` | `boolean` | yes | Whether this is the default window Hosted falls back to rather than one that was configured. `true` means no maintenance window has been set for this deployment. |
+
+---
+
## DeploymentInstance {#model-deploymentinstance}
One instance backing a deployment. A deployment has a primary and, when it has read replicas, one instance per replica.
diff --git a/specs/hosted-v1.yaml b/specs/hosted-v1.yaml
index f2dc57c..0a96a30 100644
--- a/specs/hosted-v1.yaml
+++ b/specs/hosted-v1.yaml
@@ -1328,6 +1328,103 @@ paths:
"503":
$ref: "#/components/responses/ServiceUnavailable"
+ /api/v1/deployments/{owner}/{deployment}/service-windows:
+ get:
+ operationId: listDeploymentServiceWindows
+ summary: List a deployment's service windows.
+ description: >-
+ Returns the weekly windows in which Hosted may restart the deployment's instances to
+ apply maintenance.
+
+
+ A window covers whole hours in UTC on one day of the week. `start_hour_utc` is
+ inclusive and `end_hour_utc` is exclusive, so 7 and 8 mean the hour beginning
+ 07:00 UTC.
+
+
+ Every deployment has at least one. Until one is set, the list holds a single window
+ with `is_default` set: Sunday 07:00 to 08:00 UTC.
+ tags:
+ - Deployment
+ security:
+ - apiToken: []
+ parameters:
+ - name: owner
+ in: path
+ required: true
+ description: >-
+ The user or organization that owns the deployment. 3–32 characters of letters,
+ digits, hyphens, and underscores.
+ schema:
+ type: string
+ pattern: "^[-a-zA-Z0-9_]{3,32}$"
+ example: acme
+ - name: deployment
+ in: path
+ required: true
+ description: >-
+ The deployment name, unique within the owner. 3–32 characters of letters,
+ digits, hyphens, and underscores.
+ schema:
+ type: string
+ pattern: "^[-a-zA-Z0-9_]{3,32}$"
+ example: analytics
+ responses:
+ "200":
+ description: The deployment's service windows.
+ headers:
+ x-request-id:
+ $ref: "#/components/headers/RequestId"
+ content:
+ application/json:
+ schema:
+ allOf:
+ - $ref: "#/components/schemas/Envelope"
+ - type: object
+ required:
+ - data
+ properties:
+ data:
+ type: array
+ description: The deployment's service windows.
+ items:
+ $ref: "#/components/schemas/ServiceWindow"
+ examples:
+ configured:
+ summary: A window set for early Tuesday morning UTC.
+ value:
+ data:
+ - id: 7c1e9a3b-2d4f-4a6c-8b0d-1e2f3a4b5c6d
+ day_of_week: tuesday
+ start_hour_utc: 3
+ end_hour_utc: 5
+ is_default: false
+ default:
+ summary: A deployment with no window configured.
+ value:
+ data:
+ - id: 00000000-0000-0000-0000-000000000000
+ day_of_week: sunday
+ start_hour_utc: 7
+ end_hour_utc: 8
+ is_default: true
+ "400":
+ $ref: "#/components/responses/BadRequest"
+ "401":
+ $ref: "#/components/responses/Unauthorized"
+ "403":
+ $ref: "#/components/responses/Forbidden"
+ "404":
+ $ref: "#/components/responses/NotFound"
+ "405":
+ $ref: "#/components/responses/MethodNotAllowed"
+ "422":
+ $ref: "#/components/responses/UnprocessableEntity"
+ "500":
+ $ref: "#/components/responses/InternalServerError"
+ "503":
+ $ref: "#/components/responses/ServiceUnavailable"
+
/api/v1/deployments/{owner}/{deployment}/disable:
post:
operationId: disableDeployment
@@ -2882,6 +2979,77 @@ components:
- id: cpu
display_name: CPU Utilization
+ DayOfWeek:
+ type: string
+ title: DayOfWeek
+ description: The day of the week a service window falls on, in UTC.
+ enum:
+ - sunday
+ - monday
+ - tuesday
+ - wednesday
+ - thursday
+ - friday
+ - saturday
+ examples:
+ - tuesday
+
+ ServiceWindow:
+ type: object
+ title: ServiceWindow
+ description: >-
+ One weekly window in which Hosted may restart the deployment's instances for
+ maintenance.
+ required:
+ - id
+ - day_of_week
+ - start_hour_utc
+ - end_hour_utc
+ - is_default
+ properties:
+ id:
+ type: string
+ format: uuid
+ description: >-
+ The window's identifier, unique within the deployment. A default window has no
+ identifier of its own and reports the nil UUID,
+ `00000000-0000-0000-0000-000000000000`.
+ examples:
+ - 7c1e9a3b-2d4f-4a6c-8b0d-1e2f3a4b5c6d
+ day_of_week:
+ $ref: "#/components/schemas/DayOfWeek"
+ start_hour_utc:
+ type: integer
+ format: int32
+ minimum: 0
+ maximum: 23
+ description: The first hour of the window, in UTC. Inclusive.
+ examples:
+ - 3
+ end_hour_utc:
+ type: integer
+ format: int32
+ minimum: 0
+ maximum: 23
+ description: >-
+ The hour the window ends, in UTC. Exclusive, so a window of 3 to 5 covers
+ 03:00 until 05:00.
+ examples:
+ - 5
+ is_default:
+ type: boolean
+ description: >-
+ Whether this is the default window Hosted falls back to rather than one that was
+ configured. `true` means no maintenance window has been set for this deployment.
+ examples:
+ - false
+ examples:
+ - id: 7c1e9a3b-2d4f-4a6c-8b0d-1e2f3a4b5c6d
+ day_of_week: tuesday
+ start_hour_utc: 3
+ end_hour_utc: 5
+ is_default: false
+
DeploymentInstance:
type: object
title: DeploymentInstance