diff --git a/scripts/lib/openapi-docs.mjs b/scripts/lib/openapi-docs.mjs index a5cf870..49bff24 100644 --- a/scripts/lib/openapi-docs.mjs +++ b/scripts/lib/openapi-docs.mjs @@ -88,6 +88,20 @@ function createRenderer(spec, { baseUrl, tokenPlaceholder, modelsHref }) { // Examples // ------------------------------------------------------------------------- + // The JSON body of a request or response. Error responses are served as + // `application/problem+json` (RFC 9457) rather than `application/json`, so + // looking only at the latter silently drops the Problem schema from every + // error row. Fall back to any JSON-suffixed media type. + function jsonBody(carrier) { + const content = carrier?.content; + if (!content) return undefined; + return ( + content["application/json"] ?? + content["application/problem+json"] ?? + Object.entries(content).find(([type]) => /\bjson\b|\+json$/.test(type))?.[1] + ); + } + // OpenAPI 3.1 permits a media type to carry named `examples`. Prefer the one // named `default`, else the first — a hand-written example beats anything // synthesized from the schema. @@ -102,7 +116,7 @@ function createRenderer(spec, { baseUrl, tokenPlaceholder, modelsHref }) { } function requestExample(operation) { - const media = operation.requestBody?.content?.["application/json"]; + const media = jsonBody(operation.requestBody); const authored = mediaTypeExample(media); if (authored !== undefined) return authored; @@ -155,8 +169,8 @@ function createRenderer(spec, { baseUrl, tokenPlaceholder, modelsHref }) { return `?${pairs.join("&")}`; } - function curlExample(method, path, operation) { - const url = `${baseUrl}${path}${requiredQueryString(operation.parameters)}`; + function curlExample(method, path, operation, params) { + const url = `${baseUrl}${path}${requiredQueryString(params ?? operation.parameters)}`; const lines = [ `curl -X ${METHOD_LABELS[method] ?? method.toUpperCase()} '${url}'`, ` -H 'Authorization: Bearer ${tokenPlaceholder}'`, @@ -178,7 +192,7 @@ function createRenderer(spec, { baseUrl, tokenPlaceholder, modelsHref }) { const rawResp = rawResponses[successCode]; const resp = rawResp.$ref ? resolveRef(rawResp.$ref) : rawResp; - const media = resp.content?.["application/json"]; + 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. @@ -247,7 +261,7 @@ function createRenderer(spec, { baseUrl, tokenPlaceholder, modelsHref }) { function requestBodySection(requestBody) { if (!requestBody) return ""; - const schema = requestBody.content?.["application/json"]?.schema; + const schema = jsonBody(requestBody)?.schema; if (!schema) return ""; const resolved = deref(schema); const required = resolved.required ?? []; @@ -286,7 +300,7 @@ function createRenderer(spec, { baseUrl, tokenPlaceholder, modelsHref }) { // Resolve top-level $ref (e.g. $ref: "#/components/responses/Unauthorized") const resp = rawResp.$ref ? resolveRef(rawResp.$ref) : rawResp; const desc = escapeMarkdown(resp.description ?? ""); - const schema = resp.content?.["application/json"]?.schema; + const schema = jsonBody(resp)?.schema; const name = responseSchemaName(schema); const schemaLink = name ? `[\`${name}\`](${modelsHref}#model-${name.replace("[]", "").toLowerCase()})` @@ -297,7 +311,26 @@ function createRenderer(spec, { baseUrl, tokenPlaceholder, modelsHref }) { return `\n**Responses**\n\n| Status | Description | Schema |\n|--------|-------------|--------|\n${rows}\n`; } - function endpointBlock(method, path, operation, headingLevel = "###") { + // Parameters declared on the path item apply to every operation under it + // (OpenAPI 3.1 §4.8.9) — a spec typically hoists them there once a path has + // more than one method. Operation-level entries override an inherited one + // with the same name and location. + function effectiveParameters(pathParams, opParams) { + const merged = []; + const seen = new Map(); // "in:name" -> index in merged + for (const raw of [...(pathParams ?? []), ...(opParams ?? [])]) { + const p = deref(raw); + const key = `${p.in}:${p.name}`; + if (seen.has(key)) merged[seen.get(key)] = p; + else { + seen.set(key, merged.length); + merged.push(p); + } + } + return merged; + } + + function endpointBlock(method, path, operation, headingLevel = "###", pathParams) { const anchor = `{#${operation.operationId}}`; const title = operation.summary ? operation.summary.replace(/\.$/, "") @@ -309,11 +342,12 @@ function createRenderer(spec, { baseUrl, tokenPlaceholder, modelsHref }) { operation.description.trim() !== (operation.summary ?? "").trim() ? `${operation.description.trim()}\n\n` : ""; - const params = parametersSection(operation.parameters); + const allParams = effectiveParameters(pathParams, operation.parameters); + const params = parametersSection(allParams); const body = requestBodySection(deref(operation.requestBody ?? {})); const responses = responsesSection(operation.responses); const successExample = successExampleBlock(operation.responses); - const curl = `\n**Example request**\n\n\`\`\`sh\n${curlExample(method, path, operation)}\n\`\`\`\n`; + const curl = `\n**Example request**\n\n\`\`\`sh\n${curlExample(method, path, operation, allParams)}\n\`\`\`\n`; return [ heading, methodPath, @@ -412,14 +446,20 @@ export function generateApiDocs(config) { modelsHref: models.href, }); - // Collect operations by tag. - const byTag = {}; // tag → [{ method, path, operation }] + // Collect operations by tag. pathParams carries the path item's own + // parameters, which every operation under it inherits. + const byTag = {}; // tag → [{ method, path, operation, pathParams }] for (const [path, pathItem] of Object.entries(spec.paths ?? {})) { for (const method of HTTP_METHODS) { const op = pathItem[method]; if (!op) continue; for (const tag of op.tags ?? ["Untagged"]) { - (byTag[tag] ??= []).push({ method, path, operation: op }); + (byTag[tag] ??= []).push({ + method, + path, + operation: op, + pathParams: pathItem.parameters, + }); } } } @@ -443,7 +483,7 @@ export function generateApiDocs(config) { .map((g) => { const endpoints = grouped[g] .map((item) => - endpointBlock(item.method, item.path, item.operation, "###") + endpointBlock(item.method, item.path, item.operation, "###", item.pathParams) ) .join("\n---\n\n"); return `## ${g}\n\n${endpoints}`; @@ -453,7 +493,9 @@ export function generateApiDocs(config) { } const content = items - .map((item) => endpointBlock(item.method, item.path, item.operation, "##")) + .map((item) => + endpointBlock(item.method, item.path, item.operation, "##", item.pathParams) + ) .join("\n---\n\n"); return `${frontmatter}\n\n${intro}${content}\n`; } diff --git a/site/dolt/src/content/products/dolthub/api/v2/models.md b/site/dolt/src/content/products/dolthub/api/v2/models.md index aa9159d..fc7ab45 100644 --- a/site/dolt/src/content/products/dolthub/api/v2/models.md +++ b/site/dolt/src/content/products/dolthub/api/v2/models.md @@ -33,7 +33,7 @@ A structured error body returned for every non-2xx response, following RFC 9457 | Field | Type | Required | Description | |-------|------|----------|-------------| -| `type` | `string` | no | A URI identifying the problem type; when dereferenced it points at human-readable documentation for the error. | +| `type` | `string` | yes | A URI identifying the problem type; when dereferenced it points at human-readable documentation for the error. | | `title` | `string` | yes | A short, human-readable summary of the problem type. | | `status` | `integer` | yes | The HTTP status code, repeated in the body for convenience. | | `detail` | `string` | no | A human-readable explanation specific to this occurrence of the problem. | 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 6102f9f..f833f40 100644 --- a/site/dolt/src/content/products/hosted/api/v1/README.md +++ b/site/dolt/src/content/products/hosted/api/v1/README.md @@ -46,6 +46,11 @@ See [Authentication](/products/hosted/api/v1/authentication) for how to create a | **GET** | `/api/v1/deployments/{owner}` | [List an owner's deployments](/products/hosted/api/v1/deployment#listDeployments) | | **GET** | `/api/v1/deployments/{owner}/{deployment}` | [Get a deployment](/products/hosted/api/v1/deployment#getDeployment) | | **GET** | `/api/v1/deployments/{owner}/{deployment}/instances` | [List a deployment's instances](/products/hosted/api/v1/deployment#listDeploymentInstances) | +| **POST** | `/api/v1/deployments/{owner}/{deployment}/instances` | [Add a read replica to a deployment](/products/hosted/api/v1/deployment#addDeploymentInstance) | +| **DELETE** | `/api/v1/deployments/{owner}/{deployment}/instances/{id}` | [Remove an instance from a deployment](/products/hosted/api/v1/deployment#deleteDeploymentInstance) | +| **GET** | `/api/v1/deployments/{owner}/{deployment}/config` | [Get a deployment's configuration](/products/hosted/api/v1/deployment#getDeploymentConfig) | +| **GET** | `/api/v1/deployments/{owner}/{deployment}/backups` | [List a deployment's backups](/products/hosted/api/v1/deployment#listDeploymentBackups) | +| **POST** | `/api/v1/deployments/{owner}/{deployment}/disable` | [Disable a deployment](/products/hosted/api/v1/deployment#disableDeployment) | ## Response shape @@ -92,9 +97,13 @@ Every response, including successful ones, carries an `x-request-id` header, ech Creating a deployment returns `202 Accepted` with the deployment in its `starting` state — provisioning continues after the response. Poll [Get a deployment](/products/hosted/api/v1/deployment#getDeployment) until `state` becomes `started`. +[Disabling a deployment](/products/hosted/api/v1/deployment#disableDeployment) works the same way: `202 Accepted` with the deployment in `stopping`, then poll until `state` is `stopped`. + +Instance changes are also `202`, but there is no per-instance `state` field to poll, so they are observed through the [instance list](/products/hosted/api/v1/deployment#listDeploymentInstances) instead. After [adding a replica](/products/hosted/api/v1/deployment#addDeploymentInstance), poll until that instance reports a `host` — that is when it is reachable. After [removing one](/products/hosted/api/v1/deployment#deleteDeploymentInstance), poll until it disappears from the list, which only reports instances that aren't stopped. + Deployment names are unique within an owner, which makes creates idempotent by name: retrying after an ambiguous failure returns `409 Conflict` rather than provisioning a second deployment. -> **Creating a deployment incurs cost.** +> **Creating a deployment incurs cost.** Disabling one tears down its instances and their storage — [take a backup first](/products/hosted/api/v1/deployment#listDeploymentBackups) if you want the data. ## Stability 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 b6b5a32..961f128 100644 --- a/site/dolt/src/content/products/hosted/api/v1/deployment.md +++ b/site/dolt/src/content/products/hosted/api/v1/deployment.md @@ -41,12 +41,12 @@ curl -X GET 'https://hosted.doltdb.com/api/v1/deployment-options?cloud=aws' \ | Status | Description | Schema | |--------|-------------|--------| | `200` | The available options, narrowed by the supplied parameters. | [`DeploymentOptions`](/products/hosted/api/v1/models#model-deploymentoptions) | -| `400` | The request was malformed or failed input validation. | | -| `401` | Authentication credentials were missing or invalid. | | -| `405` | The HTTP method is not supported for this resource. | | -| `422` | The request was well-formed but semantically invalid. | | -| `500` | An unexpected server error occurred. | | -| `503` | The service is temporarily unavailable. | | +| `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) | +| `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 response `200`** @@ -129,14 +129,14 @@ curl -X POST 'https://hosted.doltdb.com/api/v1/deployments' \ | Status | Description | Schema | |--------|-------------|--------| | `202` | The deployment has been accepted and is provisioning. `state` is `starting`. | [`Deployment`](/products/hosted/api/v1/models#model-deployment) | -| `400` | The request was malformed or failed input validation. | | -| `401` | Authentication credentials were missing or invalid. | | -| `403` | Authenticated, but not permitted to perform this action. | | -| `405` | The HTTP method is not supported for this resource. | | -| `409` | The request conflicts with the current state of the resource (e.g. it already exists). | | -| `422` | The request was well-formed but semantically invalid. | | -| `500` | An unexpected server error occurred. | | -| `503` | The service is temporarily unavailable. | | +| `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) | +| `405` | The HTTP method is not supported for this resource. | [`Problem`](/products/hosted/api/v1/models#model-problem) | +| `409` | The request conflicts with the current state of the resource (e.g. it already exists). | [`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 response `202`** @@ -194,12 +194,12 @@ curl -X GET 'https://hosted.doltdb.com/api/v1/deployments/{owner}' \ | Status | Description | Schema | |--------|-------------|--------| | `200` | The owner's deployments. | [`DeploymentSummary[]`](/products/hosted/api/v1/models#model-deploymentsummary) | -| `400` | The request was malformed or failed input validation. | | -| `401` | Authentication credentials were missing or invalid. | | -| `403` | Authenticated, but not permitted to perform this action. | | -| `404` | The requested resource does not exist. | | -| `405` | The HTTP method is not supported for this resource. | | -| `500` | An unexpected server error occurred. | | +| `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) | +| `500` | An unexpected server error occurred. | [`Problem`](/products/hosted/api/v1/models#model-problem) | **Example response `200`** @@ -255,12 +255,12 @@ curl -X GET 'https://hosted.doltdb.com/api/v1/deployments/{owner}/{deployment}' | Status | Description | Schema | |--------|-------------|--------| | `200` | The deployment. | [`Deployment`](/products/hosted/api/v1/models#model-deployment) | -| `400` | The request was malformed or failed input validation. | | -| `401` | Authentication credentials were missing or invalid. | | -| `404` | The requested resource does not exist. | | -| `405` | The HTTP method is not supported for this resource. | | -| `500` | An unexpected server error occurred. | | -| `503` | The service is temporarily unavailable. | | +| `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) | +| `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) | +| `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`** @@ -324,12 +324,12 @@ curl -X GET 'https://hosted.doltdb.com/api/v1/deployments/{owner}/{deployment}/i | Status | Description | Schema | |--------|-------------|--------| | `200` | The deployment's non-stopped instances. | [`DeploymentInstance[]`](/products/hosted/api/v1/models#model-deploymentinstance) | -| `400` | The request was malformed or failed input validation. | | -| `401` | Authentication credentials were missing or invalid. | | -| `404` | The requested resource does not exist. | | -| `405` | The HTTP method is not supported for this resource. | | -| `500` | An unexpected server error occurred. | | -| `503` | The service is temporarily unavailable. | | +| `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) | +| `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) | +| `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`** @@ -360,3 +360,308 @@ curl -X GET 'https://hosted.doltdb.com/api/v1/deployments/{owner}/{deployment}/i } ``` +--- + +## Add a read replica to a deployment {#addDeploymentInstance} +POST /api/v1/deployments/{owner}/{deployment}/instances + +Adds an instance to `{owner}/{deployment}` and returns `202` with the new instance, which is still being provisioned and so has no `host` yet. Poll `GET /api/v1/deployments/{owner}/{deployment}/instances` until that instance reports a `host`; that is when it is reachable. There is no per-instance state field to watch. + +This is also how a disabled deployment is started again: adding an instance clears the shutdown and brings it back to `starting`. Pass `backup_id` to restore a backup into it, or it comes back empty. + +`instance_type_id` and `volume_type_id` take the **ids** from the deployment options endpoint, not the display names an instance reports on a read. + +Instances can only be added when the deployment is settled. If it is stopping, or any instance is still starting or stopping, the request conflicts with the deployment's current state and is rejected with `409`. Retry once it settles. + +As with any create, a `5xx` does not tell you whether the instance was added. List the instances to find out; a retry while the new instance is still starting is rejected with `409` rather than adding a second one. + +**Adding a replica incurs cost.** + + +**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. | + +**Request body** + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `instance_type_id` | string | yes | The **id** of the instance type, from the deployment options endpoint. Note an instance reports `instance_type_name` on a read — the id and the display name are different values. | +| `volume_type_id` | string | yes | The **id** of the storage type, from the deployment options endpoint. | +| `volume_size_gb` | integer | yes | The size of the instance's storage volume, in gigabytes. Must fall within the selected storage type's supported range. | +| `backup_id` | string | no | A backup of this deployment to restore into the new instance, from the backups list. Only valid when the deployment is disabled and this request is restarting it; supplying it otherwise is a `400`. Without it a restarted deployment comes up empty. | + +**Example request** + +```sh +curl -X POST 'https://hosted.doltdb.com/api/v1/deployments/{owner}/{deployment}/instances' \ + -H 'Authorization: Bearer YOUR_TOKEN' \ + -H 'Content-Type: application/json' \ + -d '{"instance_type_id":"aws.t2.medium","volume_type_id":"aws.ebs.gp3_50","volume_size_gb":50}' +``` + +**Responses** + +| Status | Description | Schema | +|--------|-------------|--------| +| `202` | The instance has been accepted and is starting. | [`DeploymentInstance`](/products/hosted/api/v1/models#model-deploymentinstance) | +| `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) | +| `409` | The request conflicts with the current state of the resource (e.g. it already exists). | [`Problem`](/products/hosted/api/v1/models#model-problem) | +| `500` | An unexpected server error occurred. | [`Problem`](/products/hosted/api/v1/models#model-problem) | + +**Example response `202`** + +```json +{ + "data": { + "id": "2c4e6a8b-1d3f-4a5c-8e9b-0f1a2b3c4d5e", + "index": 1, + "is_primary": false, + "instance_type_name": "t2.medium", + "volume_type_name": "Trial 50GB EBS", + "volume_size_gb": 50 + } +} +``` + +--- + +## List a deployment's backups {#listDeploymentBackups} +GET /api/v1/deployments/{owner}/{deployment}/backups + +Returns the backups held for `{owner}/{deployment}`, newest first. Deleted backups are not included. + +The list is not paginated: a deployment's retained backups are a bounded set. + + +**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}/backups' \ + -H 'Authorization: Bearer YOUR_TOKEN' +``` + +**Responses** + +| Status | Description | Schema | +|--------|-------------|--------| +| `200` | The deployment's backups. | [`Backup[]`](/products/hosted/api/v1/models#model-backup) | +| `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) | +| `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) | +| `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`** + +```json +{ + "data": [ + { + "id": "20260812T020000.000", + "databases": [ + "analytics", + "staging" + ], + "instance_index": 0, + "created_at": "2026-08-12T02:00:00Z" + }, + { + "id": "20260811T020000.000", + "databases": [ + "analytics", + "staging" + ], + "size_bytes": 1048576, + "instance_index": 0, + "created_at": "2026-08-11T02:00:00Z" + } + ] +} +``` + +--- + +## Get a deployment's configuration {#getDeploymentConfig} +GET /api/v1/deployments/{owner}/{deployment}/config + +Returns the deployment's effective database configuration: every setting Hosted supports, carrying the deployment's own value where it has overridden one and the default otherwise. This is what the deployment's Configuration page shows. + +`is_overridden` distinguishes the two, and `default` is always reported, so a caller can tell what has been changed and what it would revert to. + +Values are strings as stored, including numeric and boolean settings. + + +**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}/config' \ + -H 'Authorization: Bearer YOUR_TOKEN' +``` + +**Responses** + +| Status | Description | Schema | +|--------|-------------|--------| +| `200` | The deployment's effective configuration — every supported setting, at the value it is running. | [`DeploymentConfig`](/products/hosted/api/v1/models#model-deploymentconfig) | +| `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) | +| `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) | +| `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`** + +```json +{ + "data": { + "settings": [ + { + "key": "listener_max_connections", + "value": "500", + "default": "100", + "is_overridden": true + }, + { + "key": "behavior_read_only", + "value": "false", + "default": "false", + "is_overridden": false + } + ] + } +} +``` + +--- + +## Disable a deployment {#disableDeployment} +POST /api/v1/deployments/{owner}/{deployment}/disable + +Shuts the deployment down, tearing down its instances and their storage. Returns `202` with the deployment in `stopping`; poll `GET /api/v1/deployments/{owner}/{deployment}` until `state` is `stopped`. + +**Take a backup first if you want the data.** + +The deployment itself is not deleted. It stays readable with `disabled_at` and `disabled_by` set — which is why this is a `POST` to an action rather than a `DELETE` — and can be brought back by adding an instance with `POST /api/v1/deployments/{owner}/{deployment}/instances`; give that request a `backup_name` to restore the data, or it comes back empty. + +Not idempotent: disabling a deployment that is already stopping or stopped returns `422`. The `202` only confirms acceptance — `GET` the deployment for its full state. + + +**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 POST 'https://hosted.doltdb.com/api/v1/deployments/{owner}/{deployment}/disable' \ + -H 'Authorization: Bearer YOUR_TOKEN' \ + -H 'Content-Type: application/json' +``` + +**Responses** + +| Status | Description | Schema | +|--------|-------------|--------| +| `202` | The teardown has been accepted. `state` is `stopping`. | [`DisableAccepted`](/products/hosted/api/v1/models#model-disableaccepted) | +| `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 response `202`** + +```json +{ + "data": { + "owner": "acme", + "name": "analytics", + "state": "stopping" + } +} +``` + +--- + +## Remove an instance from a deployment {#deleteDeploymentInstance} +DELETE /api/v1/deployments/{owner}/{deployment}/instances/{id} + +Removes an instance from `{owner}/{deployment}` and returns `202`. The instance is marked stopping and torn down in the background. + +There is no per-instance state on this API, so completion is observed by the instance leaving `GET /api/v1/deployments/{owner}/{deployment}/instances` — that list reports only instances that have not stopped. An instance that is still present is either running or still stopping. + +Instances can only be removed when the deployment is settled. If it is stopping, or any instance is still starting or stopping, the request conflicts with the deployment's current state and is rejected with `409`. Retry once it settles. Removing an instance that has already stopped is `422`. + +**This removes a database server and the data on its volume.** It is meant for removing a read replica, so check `is_primary` on the instances list before picking an id: removing the primary shuts the deployment down. To do that, use `POST /api/v1/deployments/{owner}/{deployment}/disable`, which records `disabled_at` and `disabled_by` — removing the instance leaves the deployment with nothing running and no record of why. + + +**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. | +| `id` | path | string | yes | The instance's id, as reported by the instances list. | + +**Example request** + +```sh +curl -X DELETE 'https://hosted.doltdb.com/api/v1/deployments/{owner}/{deployment}/instances/{id}' \ + -H 'Authorization: Bearer YOUR_TOKEN' +``` + +**Responses** + +| Status | Description | Schema | +|--------|-------------|--------| +| `202` | The removal has been accepted and the instance is stopping. | [`InstanceDeleteAccepted`](/products/hosted/api/v1/models#model-instancedeleteaccepted) | +| `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) | +| `409` | The request conflicts with the current state of the resource (e.g. it already exists). | [`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) | + +**Example response `202`** + +```json +{ + "data": { + "id": "2c4e6a8b-1d3f-4a5c-8e9b-0f1a2b3c4d5e", + "state": "stopping" + } +} +``` + 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 8a498e2..325a0f9 100644 --- a/site/dolt/src/content/products/hosted/api/v1/models.md +++ b/site/dolt/src/content/products/hosted/api/v1/models.md @@ -113,6 +113,51 @@ A storage type a deployment's volume can use. --- +## ConfigSetting {#model-configsetting} +One database setting and the value this deployment runs it at. + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `key` | `string` | yes | The setting's name. | +| `value` | `string` | yes | The value in effect — the deployment's override when it has one, otherwise the default. A string even for numeric and boolean settings, as stored. | +| `default` | `string` | yes | The value this setting takes when not overridden, and what it reverts to if the override is removed. | +| `is_overridden` | `boolean` | yes | Whether the deployment has overridden this setting. When `false`, `value` equals `default`. | + +--- + +## DeploymentConfig {#model-deploymentconfig} +A deployment's effective configuration — every supported setting, with the value it is running at. + +Settings are wrapped in an object rather than returned as a bare list so the resource can gain fields without a breaking change. + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `settings` | `array` | yes | Every setting Hosted supports for this deployment, in the order the catalogue reports them. | + +--- + +## AddInstanceRequest {#model-addinstancerequest} +The instance to add to a deployment. + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `instance_type_id` | `string` | yes | The **id** of the instance type, from the deployment options endpoint. Note an instance reports `instance_type_name` on a read — the id and the display name are different values. | +| `volume_type_id` | `string` | yes | The **id** of the storage type, from the deployment options endpoint. | +| `volume_size_gb` | `integer` | yes | The size of the instance's storage volume, in gigabytes. Must fall within the selected storage type's supported range. | +| `backup_id` | `string` | no | A backup of this deployment to restore into the new instance, from the backups list. Only valid when the deployment is disabled and this request is restarting it; supplying it otherwise is a `400`. Without it a restarted deployment comes up empty. | + +--- + +## InstanceDeleteAccepted {#model-instancedeleteaccepted} +Acknowledges that an instance has been accepted for removal. + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `id` | `string` | yes | The instance that is being removed. | +| `state` | `string` | yes | Always `stopping`. The instance is being torn down; it leaves the instances list once that finishes. | + +--- + ## DeploymentInstance {#model-deploymentinstance} One instance backing a deployment. A deployment has a primary and, when it has read replicas, one instance per replica. @@ -121,7 +166,7 @@ One instance backing a deployment. A deployment has a primary and, when it has r | `id` | `string` | yes | The instance's identifier, unique within the deployment. | | `index` | `integer` | yes | The instance's position in the deployment. `0` is the first instance; replicas take the following indices. | | `is_primary` | `boolean` | yes | Whether this instance is currently the primary. Exactly one instance of a started deployment is primary, and which one can change over the deployment's life. | -| `host` | `string` | no | The hostname for this specific instance. Connect to the deployment's own `host` unless you mean to address one instance directly. | +| `host` | `string` | no | The hostname for this specific instance. Connect to the deployment's own `host` unless you mean to address one instance directly. Absent until the instance has come up and reported its address, so an instance that is still being provisioned has no `host`. There is no per-instance state on this API; `host` appearing is what tells you a newly added instance is reachable. | | `instance_type_name` | `string` | no | The display name of this instance's type. | | `volume_type_name` | `string` | no | The display name of this instance's storage type. | | `volume_size_gb` | `integer` | no | The size of this instance's storage volume, in gigabytes. | @@ -178,6 +223,19 @@ The deployment's lifecycle state. `starting` covers both initial provisioning an --- +## Backup {#model-backup} +A stored backup of a deployment's databases. + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `id` | `string` | yes | The backup's identifier, unique within the deployment. Derived from the time it was taken. | +| `databases` | `array` | yes | The databases captured in this backup. Empty if the deployment had none at the time. | +| `size_bytes` | `integer` | no | The backup's size in bytes. Absent until it has been measured, which happens asynchronously after the backup is taken — so a recent backup legitimately has no size yet. | +| `instance_index` | `integer` | yes | The index of the deployment instance the backup was taken from. | +| `created_at` | `string` | yes | When the backup was taken. | + +--- + ## CloudProvider {#model-cloudprovider} The cloud the deployment runs in. @@ -243,6 +301,17 @@ This is deliberately not the same shape as `Deployment`. The list RPC returns a --- +## DisableAccepted {#model-disableaccepted} +Confirmation that a deployment's shutdown was accepted. Deliberately minimal: it reports only what is certain once the shutdown commits. `GET` the deployment for its full state. + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `owner` | `string` | yes | The user or organization that owns the deployment. | +| `name` | `string` | yes | The deployment name. | +| `state` | `string` | yes | The deployment's lifecycle state. `starting` covers both initial provisioning and a restart; poll this field to observe a create or a resize reaching `started`. | + +--- + ## Deployment {#model-deployment} A Hosted Dolt deployment. @@ -274,6 +343,6 @@ Provider-specific private-networking configuration (AWS PrivateLink, GCP Private | `caller_role` | `string` | yes | The authenticated caller's role on this deployment. Always present on a read, since a caller without at least read access cannot retrieve the deployment at all. | | `created_by` | `string` | no | The username of the user who created the deployment. | | `created_at` | `string` | yes | When the deployment was created. | -| `destroy_at` | `string` | no | When the deployment is scheduled to be destroyed. Absent unless a destroy has been scheduled. | -| `destroyed_by` | `string` | no | The username of the user who destroyed the deployment. Absent unless it has been destroyed. | +| `disabled_at` | `string` | no | When the deployment is scheduled to shut down. Absent unless it has been disabled. | +| `disabled_by` | `string` | no | The username of the user who disabled the deployment. Absent unless it has been disabled. | diff --git a/site/dolt/src/content/products/hosted/api/v1/user.md b/site/dolt/src/content/products/hosted/api/v1/user.md index 916cfe5..136b7bf 100644 --- a/site/dolt/src/content/products/hosted/api/v1/user.md +++ b/site/dolt/src/content/products/hosted/api/v1/user.md @@ -25,9 +25,9 @@ curl -X GET 'https://hosted.doltdb.com/api/v1/user' \ | Status | Description | Schema | |--------|-------------|--------| | `200` | The authenticated user's profile. | [`User`](/products/hosted/api/v1/models#model-user) | -| `401` | Authentication credentials were missing or invalid. | | -| `405` | The HTTP method is not supported for this resource. | | -| `500` | An unexpected server error occurred. | | +| `401` | Authentication credentials were missing or invalid. | [`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) | +| `500` | An unexpected server error occurred. | [`Problem`](/products/hosted/api/v1/models#model-problem) | **Example response `200`** diff --git a/specs/dolthub-v2.yaml b/specs/dolthub-v2.yaml index 571e028..dc51f98 100644 --- a/specs/dolthub-v2.yaml +++ b/specs/dolthub-v2.yaml @@ -1885,6 +1885,17 @@ paths: $ref: "#/components/responses/InternalServerError" components: + headers: + RequestId: + description: >- + The request identifier — echoed on every response (including 2xx). Reuse the + inbound `x-request-id` from the caller / edge proxy when present; otherwise the + server mints a `req_` value. + schema: + type: string + examples: + - req_2f1c9e7a-3b6d-4c5e-8a9f-0d1e2f3a4b5c + # securitySchemes model the credential types the v2 auth layer accepts. Personal # access tokens and OAuth access tokens are both presented in the `Authorization` header. # The public API does not support browser session cookies. @@ -1943,6 +1954,10 @@ components: (Problem Details for HTTP APIs). This is the single error model for the entire v2 API — there are no ad-hoc error shapes. required: + # `type` is required even though RFC 9457 permits omitting it: this API always emits + # it, and leaving it optional made the spec disagree with the generated DTO (which + # marks it non-optional because of the `default`). + - type - title - status - code @@ -2051,6 +2066,11 @@ components: properties: data: type: [object, array] + # Required for `data` to actually be unconstrained. Without it, openapi-typescript + # emits `Record` — an object permitting no properties — and the + # composed `Envelope & { data: Resource }` becomes unassignable, so nothing can use + # the generated response type. + additionalProperties: true description: The primary response payload — a resource, or an array of resources for list endpoints. meta: $ref: "#/components/schemas/Meta" @@ -3479,72 +3499,100 @@ components: responses: BadRequest: description: The request was malformed or failed input validation. + headers: + x-request-id: + $ref: "#/components/headers/RequestId" content: - application/json: + application/problem+json: schema: $ref: "#/components/schemas/Problem" Unauthorized: description: Authentication credentials were missing or invalid. + headers: + x-request-id: + $ref: "#/components/headers/RequestId" content: - application/json: + application/problem+json: schema: $ref: "#/components/schemas/Problem" Forbidden: description: Authenticated, but not permitted to perform this action. + headers: + x-request-id: + $ref: "#/components/headers/RequestId" content: - application/json: + application/problem+json: schema: $ref: "#/components/schemas/Problem" NotFound: description: The requested resource does not exist. + headers: + x-request-id: + $ref: "#/components/headers/RequestId" content: - application/json: + application/problem+json: schema: $ref: "#/components/schemas/Problem" MethodNotAllowed: description: The HTTP method is not supported for this resource. headers: + x-request-id: + $ref: "#/components/headers/RequestId" Allow: description: The HTTP methods supported by this resource. schema: type: string content: - application/json: + application/problem+json: schema: $ref: "#/components/schemas/Problem" Conflict: description: The request conflicts with the current state of the resource (e.g. it already exists). + headers: + x-request-id: + $ref: "#/components/headers/RequestId" content: - application/json: + application/problem+json: schema: $ref: "#/components/schemas/Problem" UnprocessableEntity: description: The request was well-formed but semantically invalid. + headers: + x-request-id: + $ref: "#/components/headers/RequestId" content: - application/json: + application/problem+json: schema: $ref: "#/components/schemas/Problem" TooManyRequests: description: The client has exceeded its rate limit. headers: + x-request-id: + $ref: "#/components/headers/RequestId" Retry-After: description: The number of seconds to wait before retrying the request. schema: type: integer format: int32 content: - application/json: + application/problem+json: schema: $ref: "#/components/schemas/Problem" InternalServerError: description: An unexpected server error occurred. + headers: + x-request-id: + $ref: "#/components/headers/RequestId" content: - application/json: + application/problem+json: schema: $ref: "#/components/schemas/Problem" ServiceUnavailable: description: The service is temporarily unavailable. + headers: + x-request-id: + $ref: "#/components/headers/RequestId" content: - application/json: + application/problem+json: schema: $ref: "#/components/schemas/Problem" diff --git a/specs/hosted-v1.yaml b/specs/hosted-v1.yaml index 56bb91c..116e6c5 100644 --- a/specs/hosted-v1.yaml +++ b/specs/hosted-v1.yaml @@ -503,6 +503,27 @@ paths: $ref: "#/components/responses/ServiceUnavailable" /api/v1/deployments/{owner}/{deployment}/instances: + 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 get: operationId: listDeploymentInstances summary: List a deployment's instances. @@ -523,6 +544,165 @@ paths: - Deployment security: - apiToken: [] + responses: + "200": + description: The deployment's non-stopped instances. + 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 non-stopped instances. + items: + $ref: "#/components/schemas/DeploymentInstance" + examples: + default: + summary: A primary with one read replica. + value: + data: + - id: 9b1f5c2e-4d3a-4f8b-9c0d-1e2f3a4b5c6d + index: 0 + is_primary: true + host: analytics-0.dbs.hosted.doltdb.com + instance_type_name: t2.medium + volume_type_name: Trial 50GB EBS + volume_size_gb: 50 + hourly_cost_usd: 0.06849315 + - id: 2c4e6a8b-1d3f-4a5c-8e9b-0f1a2b3c4d5e + index: 1 + is_primary: false + host: analytics-1.dbs.hosted.doltdb.com + instance_type_name: t2.medium + volume_type_name: Trial 50GB EBS + volume_size_gb: 50 + hourly_cost_usd: 0.06849315 + "400": + $ref: "#/components/responses/BadRequest" + "401": + $ref: "#/components/responses/Unauthorized" + "404": + $ref: "#/components/responses/NotFound" + "405": + $ref: "#/components/responses/MethodNotAllowed" + "500": + $ref: "#/components/responses/InternalServerError" + "503": + $ref: "#/components/responses/ServiceUnavailable" + + post: + operationId: addDeploymentInstance + summary: Add a read replica to a deployment. + description: >- + Adds an instance to `{owner}/{deployment}` and returns `202` with the new instance, + which is still being provisioned and so has no `host` yet. Poll + `GET /api/v1/deployments/{owner}/{deployment}/instances` until that instance reports + a `host`; that is when it is reachable. There is no per-instance state field to + watch. + + + This is also how a disabled deployment is started again: adding an instance clears + the shutdown and brings it back to `starting`. Pass `backup_id` to restore a backup + into it, or it comes back empty. + + + `instance_type_id` and `volume_type_id` take the **ids** from the deployment options + endpoint, not the display names an instance reports on a read. + + + Instances can only be added when the deployment is settled. If it is stopping, or + any instance is still starting or stopping, the request conflicts with the + deployment's current state and is rejected with `409`. Retry once it settles. + + + As with any create, a `5xx` does not tell you whether the instance was added. List + the instances to find out; a retry while the new instance is still starting is + rejected with `409` rather than adding a second one. + + + **Adding a replica incurs cost.** + tags: + - Deployment + security: + - apiToken: [] + requestBody: + required: true + description: The instance to add. + content: + application/json: + schema: + $ref: "#/components/schemas/AddInstanceRequest" + examples: + default: + summary: A trial-tier replica. + value: + instance_type_id: aws.t2.medium + volume_type_id: aws.ebs.gp3_50 + volume_size_gb: 50 + responses: + "202": + description: The instance has been accepted and is starting. + headers: + x-request-id: + $ref: "#/components/headers/RequestId" + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/Envelope" + - type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/DeploymentInstance" + examples: + default: + summary: The replica just added. + value: + data: + id: 2c4e6a8b-1d3f-4a5c-8e9b-0f1a2b3c4d5e + index: 1 + is_primary: false + instance_type_name: t2.medium + volume_type_name: Trial 50GB EBS + volume_size_gb: 50 + "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" + "409": + $ref: "#/components/responses/Conflict" + "500": + $ref: "#/components/responses/InternalServerError" + /api/v1/deployments/{owner}/{deployment}/backups: + get: + operationId: listDeploymentBackups + summary: List a deployment's backups. + description: >- + Returns the backups held for `{owner}/{deployment}`, newest first. Deleted backups + are not included. + + + The list is not paginated: a deployment's retained backups are a bounded set. + tags: + - Deployment + security: + - apiToken: [] parameters: - name: owner in: path @@ -546,7 +726,7 @@ paths: example: analytics responses: "200": - description: The deployment's non-stopped instances. + description: The deployment's backups. headers: x-request-id: $ref: "#/components/headers/RequestId" @@ -561,30 +741,27 @@ paths: properties: data: type: array - description: The deployment's non-stopped instances. + description: The deployment's backups. items: - $ref: "#/components/schemas/DeploymentInstance" + $ref: "#/components/schemas/Backup" examples: default: - summary: A primary with one read replica. + summary: Two retained backups. value: data: - - id: 9b1f5c2e-4d3a-4f8b-9c0d-1e2f3a4b5c6d - index: 0 - is_primary: true - host: analytics-0.dbs.hosted.doltdb.com - instance_type_name: t2.medium - volume_type_name: Trial 50GB EBS - volume_size_gb: 50 - hourly_cost_usd: 0.06849315 - - id: 2c4e6a8b-1d3f-4a5c-8e9b-0f1a2b3c4d5e - index: 1 - is_primary: false - host: analytics-1.dbs.hosted.doltdb.com - instance_type_name: t2.medium - volume_type_name: Trial 50GB EBS - volume_size_gb: 50 - hourly_cost_usd: 0.06849315 + - id: 20260812T020000.000 + databases: + - analytics + - staging + instance_index: 0 + created_at: "2026-08-12T02:00:00Z" + - id: 20260811T020000.000 + databases: + - analytics + - staging + size_bytes: 1048576 + instance_index: 0 + created_at: "2026-08-11T02:00:00Z" "400": $ref: "#/components/responses/BadRequest" "401": @@ -598,6 +775,284 @@ paths: "503": $ref: "#/components/responses/ServiceUnavailable" + /api/v1/deployments/{owner}/{deployment}/config: + get: + operationId: getDeploymentConfig + summary: Get a deployment's configuration. + description: >- + Returns the deployment's effective database configuration: every setting Hosted + supports, carrying the deployment's own value where it has overridden one and the + default otherwise. This is what the deployment's Configuration page shows. + + + `is_overridden` distinguishes the two, and `default` is always reported, so a caller + can tell what has been changed and what it would revert to. + + + Values are strings as stored, including numeric and boolean settings. + 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 effective configuration — every supported setting, at the value + it is running. + headers: + x-request-id: + $ref: "#/components/headers/RequestId" + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/Envelope" + - type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/DeploymentConfig" + examples: + default: + summary: One overridden setting and one on its default. + value: + data: + settings: + - key: listener_max_connections + value: "500" + default: "100" + is_overridden: true + - key: behavior_read_only + value: "false" + default: "false" + is_overridden: false + "400": + $ref: "#/components/responses/BadRequest" + "401": + $ref: "#/components/responses/Unauthorized" + "404": + $ref: "#/components/responses/NotFound" + "405": + $ref: "#/components/responses/MethodNotAllowed" + "500": + $ref: "#/components/responses/InternalServerError" + "503": + $ref: "#/components/responses/ServiceUnavailable" + + /api/v1/deployments/{owner}/{deployment}/disable: + post: + operationId: disableDeployment + summary: Disable a deployment. + description: >- + Shuts the deployment down, tearing down its instances and their storage. Returns + `202` with the deployment in `stopping`; poll + `GET /api/v1/deployments/{owner}/{deployment}` until `state` is `stopped`. + + + **Take a backup first if you want the data.** + + + The deployment itself is not deleted. It stays readable with `disabled_at` and + `disabled_by` set — which is why this is a `POST` to an action rather than a + `DELETE` — and can be brought back by adding an instance with + `POST /api/v1/deployments/{owner}/{deployment}/instances`; give that request a + `backup_name` to restore the data, or it comes back empty. + + + Not idempotent: disabling a deployment that is already stopping or stopped returns + `422`. The `202` only confirms acceptance — `GET` the deployment for its full + state. + 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: + "202": + description: >- + The teardown has been accepted. `state` is `stopping`. + headers: + x-request-id: + $ref: "#/components/headers/RequestId" + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/Envelope" + - type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/DisableAccepted" + examples: + default: + summary: A deployment accepted for teardown. + value: + data: + owner: acme + name: analytics + state: stopping + "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}/instances/{id}: + 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 + - name: id + in: path + required: true + description: The instance's id, as reported by the instances list. + schema: + type: string + format: uuid + example: 2c4e6a8b-1d3f-4a5c-8e9b-0f1a2b3c4d5e + delete: + operationId: deleteDeploymentInstance + summary: Remove an instance from a deployment. + description: >- + Removes an instance from `{owner}/{deployment}` and returns `202`. The instance is + marked stopping and torn down in the background. + + + There is no per-instance state on this API, so completion is observed by the + instance leaving `GET /api/v1/deployments/{owner}/{deployment}/instances` — that + list reports only instances that have not stopped. An instance that is still present + is either running or still stopping. + + + Instances can only be removed when the deployment is settled. If it is stopping, or + any instance is still starting or stopping, the request conflicts with the + deployment's current state and is rejected with `409`. Retry once it settles. + Removing an instance that has already stopped is `422`. + + + **This removes a database server and the data on its volume.** It is meant for + removing a read replica, so check `is_primary` on the instances list before picking + an id: removing the primary shuts the deployment down. To do that, use + `POST /api/v1/deployments/{owner}/{deployment}/disable`, which records `disabled_at` + and `disabled_by` — removing the instance leaves the deployment with nothing running + and no record of why. + tags: + - Deployment + security: + - apiToken: [] + responses: + "202": + description: The removal has been accepted and the instance is stopping. + headers: + x-request-id: + $ref: "#/components/headers/RequestId" + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/Envelope" + - type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/InstanceDeleteAccepted" + examples: + default: + summary: The replica is stopping. + value: + data: + id: 2c4e6a8b-1d3f-4a5c-8e9b-0f1a2b3c4d5e + state: stopping + "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" + "409": + $ref: "#/components/responses/Conflict" + "422": + $ref: "#/components/responses/UnprocessableEntity" + "500": + $ref: "#/components/responses/InternalServerError" + components: headers: RequestId: @@ -905,6 +1360,132 @@ components: examples: - 0 + ConfigSetting: + type: object + title: ConfigSetting + description: >- + One database setting and the value this deployment runs it at. + required: + - key + - value + - default + - is_overridden + properties: + key: + type: string + description: The setting's name. + examples: + - listener_max_connections + value: + type: string + description: >- + The value in effect — the deployment's override when it has one, otherwise the + default. A string even for numeric and boolean settings, as stored. + examples: + - "500" + default: + type: string + description: >- + The value this setting takes when not overridden, and what it reverts to if the + override is removed. + examples: + - "100" + is_overridden: + type: boolean + description: >- + Whether the deployment has overridden this setting. When `false`, `value` equals + `default`. + examples: + - true + + DeploymentConfig: + type: object + title: DeploymentConfig + description: >- + A deployment's effective configuration — every supported setting, with the value it + is running at. + + + Settings are wrapped in an object rather than returned as a bare list so the + resource can gain fields without a breaking change. + required: + - settings + properties: + settings: + type: array + description: >- + Every setting Hosted supports for this deployment, in the order the catalogue + reports them. + items: + $ref: "#/components/schemas/ConfigSetting" + + AddInstanceRequest: + type: object + title: AddInstanceRequest + description: >- + The instance to add to a deployment. + required: + - instance_type_id + - volume_type_id + - volume_size_gb + properties: + instance_type_id: + type: string + description: >- + The **id** of the instance type, from the deployment options endpoint. Note an + instance reports `instance_type_name` on a read — the id and the display name are + different values. + minLength: 1 + examples: + - aws.t2.medium + volume_type_id: + type: string + description: >- + The **id** of the storage type, from the deployment options endpoint. + minLength: 1 + examples: + - aws.ebs.gp3_50 + volume_size_gb: + type: integer + format: int32 + description: >- + The size of the instance's storage volume, in gigabytes. Must fall within the + selected storage type's supported range. + minimum: 1 + maximum: 2147483647 + examples: + - 50 + backup_id: + type: string + pattern: "^[0-9]{8}T[0-9]{6}\\.[0-9]{3}$" + description: >- + A backup of this deployment to restore into the new instance, from the backups + list. Only valid when the deployment is disabled and this request is restarting + it; supplying it otherwise is a `400`. Without it a restarted deployment comes + up empty. + examples: + - 20260811T020000.000 + InstanceDeleteAccepted: + type: object + title: InstanceDeleteAccepted + description: Acknowledges that an instance has been accepted for removal. + required: + - id + - state + properties: + id: + type: string + format: uuid + description: The instance that is being removed. + examples: + - 2c4e6a8b-1d3f-4a5c-8e9b-0f1a2b3c4d5e + state: + type: string + description: >- + Always `stopping`. The instance is being torn down; it leaves the instances list + once that finishes. + enum: + - stopping DeploymentInstance: type: object title: DeploymentInstance @@ -943,6 +1524,12 @@ components: description: >- The hostname for this specific instance. Connect to the deployment's own `host` unless you mean to address one instance directly. + + + Absent until the instance has come up and reported its address, so an instance + that is still being provisioned has no `host`. There is no per-instance state on + this API; `host` appearing is what tells you a newly added instance is + reachable. examples: - analytics-0.dbs.hosted.doltdb.com instance_type_name: @@ -1076,6 +1663,7 @@ components: description: >- The size of the storage volume, in gigabytes. Must fall within the selected storage type's supported range. + maximum: 2147483647 minimum: 1 examples: - 50 @@ -1083,6 +1671,7 @@ components: type: integer format: int32 description: The number of read replicas. Defaults to `0` when omitted. + maximum: 2147483647 minimum: 0 examples: - 2 @@ -1123,6 +1712,55 @@ components: examples: - started + Backup: + type: object + title: Backup + description: A stored backup of a deployment's databases. + required: + - id + - databases + - instance_index + - created_at + properties: + id: + type: string + description: >- + The backup's identifier, unique within the deployment. Derived from the time it + was taken. + examples: + - 20260811T020000.000 + databases: + type: array + description: >- + The databases captured in this backup. Empty if the deployment had none at the + time. + items: + type: string + examples: + - ["analytics", "staging"] + size_bytes: + type: integer + format: int64 + description: >- + The backup's size in bytes. Absent until it has been measured, which happens + asynchronously after the backup is taken — so a recent backup legitimately has + no size yet. + examples: + - 1048576 + instance_index: + type: integer + format: int32 + description: >- + The index of the deployment instance the backup was taken from. + examples: + - 0 + created_at: + type: string + format: date-time + description: When the backup was taken. + examples: + - "2026-08-11T02:00:00Z" + CloudProvider: type: string title: CloudProvider @@ -1259,6 +1897,30 @@ components: examples: - "2026-08-10T02:00:00Z" + DisableAccepted: + type: object + title: DisableAccepted + description: >- + Confirmation that a deployment's shutdown was accepted. Deliberately minimal: it + reports only what is certain once the shutdown commits. `GET` the deployment for its + full state. + required: + - owner + - name + - state + properties: + owner: + type: string + description: The user or organization that owns the deployment. + examples: + - acme + name: + type: string + description: The deployment name. + examples: + - analytics + state: + $ref: "#/components/schemas/DeploymentState" Deployment: type: object title: Deployment @@ -1390,19 +2052,19 @@ components: description: When the deployment was created. examples: - "2026-07-01T18:22:04Z" - destroy_at: + disabled_at: type: string format: date-time description: >- - When the deployment is scheduled to be destroyed. Absent unless a destroy has - been scheduled. + When the deployment is scheduled to shut down. Absent unless it has been + disabled. examples: - "2026-09-01T00:00:00Z" - destroyed_by: + disabled_by: type: string description: >- - The username of the user who destroyed the deployment. Absent unless it has been - destroyed. + The username of the user who disabled the deployment. Absent unless it has been + disabled. examples: - acme-ops