{
  "components": {
    "responses": {
      "Conflict": {
        "content": {
          "application/json": {
            "schema": {
              "properties": {
                "details": {},
                "error": {
                  "const": "conflict",
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              },
              "required": [
                "error"
              ],
              "type": "object"
            }
          }
        },
        "description": "`409 conflict` — The resource is in a state that forbids the operation — for re-verify, the connection was disconnected and is no longer monitored (`details.reason` is `disconnected`)."
      },
      "Expired": {
        "content": {
          "application/json": {
            "schema": {
              "properties": {
                "details": {},
                "error": {
                  "const": "expired",
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              },
              "required": [
                "error"
              ],
              "type": "object"
            }
          }
        },
        "description": "`410 expired` — The connect session's 24-hour lifetime has elapsed. Returned before any verification, metering, or webhook work happens."
      },
      "Forbidden": {
        "content": {
          "application/json": {
            "schema": {
              "properties": {
                "details": {},
                "error": {
                  "const": "forbidden",
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              },
              "required": [
                "error"
              ],
              "type": "object"
            }
          }
        },
        "description": "`403 forbidden` — The OAuth access token was not granted the scope this operation requires. `details.code` is `SCOPE_MISSING` and `details.requiredScope` names the scope. Secret keys (`dd_sk_`) carry no scopes and never see this."
      },
      "Internal": {
        "content": {
          "application/json": {
            "schema": {
              "properties": {
                "details": {},
                "error": {
                  "const": "internal",
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              },
              "required": [
                "error"
              ],
              "type": "object"
            }
          }
        },
        "description": "`500 internal` — An unhandled server-side failure. Reported to our error tracker; safe to retry."
      },
      "InvalidRequest": {
        "content": {
          "application/json": {
            "schema": {
              "properties": {
                "details": {},
                "error": {
                  "const": "invalid_request",
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              },
              "required": [
                "error"
              ],
              "type": "object"
            }
          }
        },
        "description": "`400 invalid_request` — The request body or query failed schema validation, or a documented capability rule rejected it. `details` carries the field-level report when the failure came from schema validation."
      },
      "NotConfigured": {
        "content": {
          "application/json": {
            "schema": {
              "properties": {
                "details": {},
                "error": {
                  "const": "not_configured",
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              },
              "required": [
                "error"
              ],
              "type": "object"
            }
          }
        },
        "description": "`503 not_configured` — The one-click flow this endpoint starts is not configured on this deployment. The manual-records flow remains available."
      },
      "NotFound": {
        "content": {
          "application/json": {
            "schema": {
              "properties": {
                "details": {},
                "error": {
                  "const": "not_found",
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              },
              "required": [
                "error"
              ],
              "type": "object"
            }
          }
        },
        "description": "`404 not_found` — The addressed resource does not exist, or the caller does not own it — the two collapse to one status on purpose, so an id cannot be enumerated by a caller who does not own it."
      },
      "QuotaExceeded": {
        "content": {
          "application/json": {
            "schema": {
              "properties": {
                "details": {},
                "error": {
                  "const": "quota_exceeded",
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              },
              "required": [
                "error"
              ],
              "type": "object"
            }
          }
        },
        "description": "`402 quota_exceeded` — The team's monthly verified-connection allowance is exhausted (enforced at session creation on the Free plan)."
      },
      "RateLimited": {
        "content": {
          "application/json": {
            "schema": {
              "properties": {
                "details": {},
                "error": {
                  "const": "rate_limited",
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              },
              "required": [
                "error"
              ],
              "type": "object"
            }
          }
        },
        "description": "`429 rate_limited` — Rate limited. `Retry-After` (seconds) is set and `details.retryAfterSeconds` repeats it. Branch on `details.reason`: `request_rate` means the caller is calling too fast (`details.limit` carries the cap), `recently_checked` means this one connection was DNS-checked inside its cooldown.",
        "headers": {
          "Retry-After": {
            "description": "Whole seconds to wait before retrying. Always present on this response.",
            "required": true,
            "schema": {
              "minimum": 1,
              "type": "integer"
            }
          }
        }
      },
      "Unauthorized": {
        "content": {
          "application/json": {
            "schema": {
              "properties": {
                "details": {},
                "error": {
                  "const": "unauthorized",
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              },
              "required": [
                "error"
              ],
              "type": "object"
            }
          }
        },
        "description": "`401 unauthorized` — No `Authorization: Bearer` header, or the credential is malformed, unknown, or (for OAuth tokens) fails verification."
      }
    },
    "schemas": {
      "CheckDomainInput": {
        "properties": {
          "domain": {
            "maxLength": 253,
            "minLength": 1,
            "type": "string"
          }
        },
        "required": [
          "domain"
        ],
        "type": "object"
      },
      "CheckDomainResponse": {
        "properties": {
          "confidence": {
            "enum": [
              "high",
              "medium",
              "low"
            ],
            "type": "string"
          },
          "domain": {
            "type": "string"
          },
          "domainConnect": {
            "properties": {
              "discovered": {
                "type": "boolean"
              },
              "providerId": {
                "type": "string"
              },
              "providerName": {
                "type": "string"
              }
            },
            "required": [
              "discovered"
            ],
            "type": "object"
          },
          "guide": {
            "properties": {
              "apexToken": {
                "enum": [
                  "@",
                  "(blank)",
                  "%domain%"
                ],
                "type": "string"
              },
              "dashboardUrl": {
                "type": "string"
              },
              "hostFormat": {
                "type": "string"
              },
              "label": {
                "type": "string"
              },
              "notes": {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "provider": {
                "type": "string"
              },
              "steps": {
                "items": {
                  "type": "string"
                },
                "type": "array"
              }
            },
            "required": [
              "provider",
              "label",
              "hostFormat",
              "apexToken",
              "steps"
            ],
            "type": "object"
          },
          "label": {
            "type": "string"
          },
          "method": {
            "enum": [
              "oauth",
              "domain-connect",
              "guided"
            ],
            "type": "string"
          },
          "nameServers": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "provider": {
            "type": "string"
          },
          "tier": {
            "anyOf": [
              {
                "const": 1,
                "type": "number"
              },
              {
                "const": 2,
                "type": "number"
              },
              {
                "const": 3,
                "type": "number"
              }
            ]
          },
          "zone": {
            "type": "string"
          }
        },
        "required": [
          "domain",
          "zone",
          "provider",
          "label",
          "tier",
          "method",
          "confidence",
          "nameServers",
          "domainConnect",
          "guide"
        ],
        "type": "object"
      },
      "ConnectionSummary": {
        "properties": {
          "appId": {
            "type": "string"
          },
          "brokenAt": {
            "anyOf": [
              {
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "createdAt": {
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
            "type": "string"
          },
          "disconnectedAt": {
            "anyOf": [
              {
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "domain": {
            "type": "string"
          },
          "fqdn": {
            "type": "string"
          },
          "id": {
            "type": "string"
          },
          "lastCheckedAt": {
            "anyOf": [
              {
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "recordFqdns": {
            "items": {
              "minLength": 1,
              "type": "string"
            },
            "type": "array"
          },
          "sessionId": {
            "type": "string"
          },
          "status": {
            "enum": [
              "active",
              "broken"
            ],
            "type": "string"
          },
          "verifiedAt": {
            "anyOf": [
              {
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "id",
          "appId",
          "sessionId",
          "domain",
          "fqdn",
          "recordFqdns",
          "status",
          "verifiedAt",
          "lastCheckedAt",
          "brokenAt",
          "disconnectedAt",
          "createdAt"
        ],
        "type": "object"
      },
      "CreateSessionInput": {
        "properties": {
          "appId": {
            "minLength": 1,
            "type": "string"
          },
          "domain": {
            "maxLength": 253,
            "pattern": "^(?!-)[a-z0-9-]{1,63}(?<!-)(\\.(?!-)[a-z0-9-]{1,63}(?<!-))+$",
            "type": "string"
          },
          "recipe": {
            "type": "string"
          },
          "records": {
            "items": {
              "properties": {
                "host": {
                  "minLength": 1,
                  "type": "string"
                },
                "priority": {
                  "maximum": 9007199254740991,
                  "minimum": -9007199254740991,
                  "type": "integer"
                },
                "ttl": {
                  "maximum": 9007199254740991,
                  "minimum": -9007199254740991,
                  "type": "integer"
                },
                "type": {
                  "enum": [
                    "A",
                    "AAAA",
                    "CNAME",
                    "TXT",
                    "MX"
                  ],
                  "type": "string"
                },
                "value": {
                  "minLength": 1,
                  "type": "string"
                }
              },
              "required": [
                "type",
                "host",
                "value"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "returnUrl": {
            "format": "uri",
            "maxLength": 2048,
            "type": "string"
          }
        },
        "required": [
          "domain",
          "records"
        ],
        "type": "object"
      },
      "CreateSessionResponse": {
        "properties": {
          "connectUrl": {
            "minLength": 1,
            "type": "string"
          },
          "expiresAt": {
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
            "type": "string"
          },
          "id": {
            "type": "string"
          },
          "records": {
            "items": {
              "properties": {
                "fqdn": {
                  "minLength": 1,
                  "type": "string"
                },
                "host": {
                  "minLength": 1,
                  "type": "string"
                },
                "type": {
                  "enum": [
                    "A",
                    "AAAA",
                    "CNAME",
                    "TXT",
                    "MX"
                  ],
                  "type": "string"
                }
              },
              "required": [
                "type",
                "host",
                "fqdn"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "token": {
            "minLength": 1,
            "type": "string"
          },
          "warnings": {
            "items": {
              "properties": {
                "code": {
                  "enum": [
                    "duplicate_host_label"
                  ],
                  "type": "string"
                },
                "fqdn": {
                  "minLength": 1,
                  "type": "string"
                },
                "host": {
                  "type": "string"
                },
                "message": {
                  "minLength": 1,
                  "type": "string"
                }
              },
              "required": [
                "code",
                "message",
                "host",
                "fqdn"
              ],
              "type": "object"
            },
            "type": "array"
          }
        },
        "required": [
          "id",
          "token",
          "expiresAt",
          "connectUrl",
          "records"
        ],
        "type": "object"
      },
      "DeleteWebhookEndpointResponse": {
        "properties": {
          "deleted": {
            "const": true,
            "type": "boolean"
          },
          "id": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "deleted"
        ],
        "type": "object"
      },
      "DetectSessionResponse": {
        "properties": {
          "confidence": {
            "enum": [
              "high",
              "medium",
              "low"
            ],
            "type": "string"
          },
          "domainConnect": {
            "anyOf": [
              {
                "properties": {
                  "providerId": {
                    "type": "string"
                  },
                  "providerName": {
                    "type": "string"
                  }
                },
                "required": [
                  "providerId",
                  "providerName"
                ],
                "type": "object"
              },
              {
                "type": "null"
              }
            ]
          },
          "domainConnectReady": {
            "type": "boolean"
          },
          "guide": {
            "properties": {
              "apexToken": {
                "enum": [
                  "@",
                  "(blank)",
                  "%domain%"
                ],
                "type": "string"
              },
              "dashboardUrl": {
                "type": "string"
              },
              "hostFormat": {
                "type": "string"
              },
              "label": {
                "type": "string"
              },
              "notes": {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "provider": {
                "type": "string"
              },
              "steps": {
                "items": {
                  "type": "string"
                },
                "type": "array"
              }
            },
            "required": [
              "provider",
              "label",
              "hostFormat",
              "apexToken",
              "steps"
            ],
            "type": "object"
          },
          "label": {
            "type": "string"
          },
          "method": {
            "enum": [
              "oauth",
              "domain-connect",
              "guided"
            ],
            "type": "string"
          },
          "nameServers": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "provider": {
            "type": "string"
          },
          "tier": {
            "anyOf": [
              {
                "const": 1,
                "type": "number"
              },
              {
                "const": 2,
                "type": "number"
              },
              {
                "const": 3,
                "type": "number"
              }
            ]
          },
          "zone": {
            "type": "string"
          }
        },
        "required": [
          "provider",
          "label",
          "zone",
          "tier",
          "method",
          "confidence",
          "nameServers",
          "domainConnect",
          "domainConnectReady",
          "guide"
        ],
        "type": "object"
      },
      "DisconnectConnectionResponse": {
        "properties": {
          "alreadyDisconnected": {
            "type": "boolean"
          },
          "disconnectedAt": {
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
            "type": "string"
          },
          "id": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "disconnectedAt",
          "alreadyDisconnected"
        ],
        "type": "object"
      },
      "IntegratorSession": {
        "properties": {
          "appId": {
            "type": "string"
          },
          "connectionId": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "createdAt": {
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
            "type": "string"
          },
          "detectedProvider": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "domain": {
            "type": "string"
          },
          "expired": {
            "type": "boolean"
          },
          "expiresAt": {
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
            "type": "string"
          },
          "id": {
            "type": "string"
          },
          "recipe": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "records": {
            "items": {
              "properties": {
                "fqdn": {
                  "minLength": 1,
                  "type": "string"
                },
                "host": {
                  "minLength": 1,
                  "type": "string"
                },
                "type": {
                  "enum": [
                    "A",
                    "AAAA",
                    "CNAME",
                    "TXT",
                    "MX"
                  ],
                  "type": "string"
                }
              },
              "required": [
                "type",
                "host",
                "fqdn"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "status": {
            "type": "string"
          },
          "tier": {
            "anyOf": [
              {
                "maximum": 9007199254740991,
                "minimum": -9007199254740991,
                "type": "integer"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "id",
          "appId",
          "domain",
          "records",
          "recipe",
          "status",
          "tier",
          "detectedProvider",
          "connectionId",
          "createdAt",
          "expiresAt",
          "expired"
        ],
        "type": "object"
      },
      "ListAppsResponse": {
        "properties": {
          "apps": {
            "items": {
              "properties": {
                "brandColor": {
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "createdAt": {
                  "format": "date-time",
                  "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
                  "type": "string"
                },
                "id": {
                  "type": "string"
                },
                "logoUrl": {
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "name": {
                  "type": "string"
                },
                "publicKey": {
                  "type": "string"
                },
                "sandbox": {
                  "type": "boolean"
                }
              },
              "required": [
                "id",
                "name",
                "publicKey",
                "sandbox",
                "logoUrl",
                "brandColor",
                "createdAt"
              ],
              "type": "object"
            },
            "type": "array"
          }
        },
        "required": [
          "apps"
        ],
        "type": "object"
      },
      "ListConnectionsResponse": {
        "properties": {
          "connections": {
            "items": {
              "properties": {
                "appId": {
                  "type": "string"
                },
                "brokenAt": {
                  "anyOf": [
                    {
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "createdAt": {
                  "format": "date-time",
                  "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
                  "type": "string"
                },
                "disconnectedAt": {
                  "anyOf": [
                    {
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "domain": {
                  "type": "string"
                },
                "fqdn": {
                  "type": "string"
                },
                "id": {
                  "type": "string"
                },
                "lastCheckedAt": {
                  "anyOf": [
                    {
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "recordFqdns": {
                  "items": {
                    "minLength": 1,
                    "type": "string"
                  },
                  "type": "array"
                },
                "sessionId": {
                  "type": "string"
                },
                "status": {
                  "enum": [
                    "active",
                    "broken"
                  ],
                  "type": "string"
                },
                "verifiedAt": {
                  "anyOf": [
                    {
                      "format": "date-time",
                      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ]
                }
              },
              "required": [
                "id",
                "appId",
                "sessionId",
                "domain",
                "fqdn",
                "recordFqdns",
                "status",
                "verifiedAt",
                "lastCheckedAt",
                "brokenAt",
                "disconnectedAt",
                "createdAt"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "nextCursor": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "connections",
          "nextCursor"
        ],
        "type": "object"
      },
      "ListWebhookEndpointsResponse": {
        "properties": {
          "endpoints": {
            "items": {
              "properties": {
                "appId": {
                  "type": "string"
                },
                "createdAt": {
                  "format": "date-time",
                  "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
                  "type": "string"
                },
                "id": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                }
              },
              "required": [
                "id",
                "appId",
                "url",
                "createdAt"
              ],
              "type": "object"
            },
            "type": "array"
          }
        },
        "required": [
          "endpoints"
        ],
        "type": "object"
      },
      "PublicSession": {
        "properties": {
          "detectedProvider": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "domain": {
            "type": "string"
          },
          "expiresAt": {
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
            "type": "string"
          },
          "id": {
            "type": "string"
          },
          "recipe": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "records": {
            "items": {
              "properties": {
                "host": {
                  "minLength": 1,
                  "type": "string"
                },
                "priority": {
                  "maximum": 9007199254740991,
                  "minimum": -9007199254740991,
                  "type": "integer"
                },
                "ttl": {
                  "maximum": 9007199254740991,
                  "minimum": -9007199254740991,
                  "type": "integer"
                },
                "type": {
                  "enum": [
                    "A",
                    "AAAA",
                    "CNAME",
                    "TXT",
                    "MX"
                  ],
                  "type": "string"
                },
                "value": {
                  "minLength": 1,
                  "type": "string"
                }
              },
              "required": [
                "type",
                "host",
                "value"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "returnUrl": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "status": {
            "type": "string"
          },
          "tier": {
            "anyOf": [
              {
                "maximum": 9007199254740991,
                "minimum": -9007199254740991,
                "type": "integer"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "id",
          "domain",
          "records",
          "recipe",
          "status",
          "tier",
          "detectedProvider",
          "returnUrl",
          "expiresAt"
        ],
        "type": "object"
      },
      "ReverifyConnectionResponse": {
        "properties": {
          "accepted": {
            "const": true,
            "type": "boolean"
          }
        },
        "required": [
          "accepted"
        ],
        "type": "object"
      },
      "RotateAppSecretKeyInput": {
        "properties": {
          "overlapHours": {
            "anyOf": [
              {
                "const": 0,
                "type": "number"
              },
              {
                "const": 1,
                "type": "number"
              },
              {
                "const": 24,
                "type": "number"
              }
            ],
            "default": 0
          }
        },
        "type": "object"
      },
      "RotateAppSecretKeyResponse": {
        "properties": {
          "appId": {
            "type": "string"
          },
          "previousKeyExpiresAt": {
            "anyOf": [
              {
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "publicKey": {
            "type": "string"
          },
          "rotatedAt": {
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
            "type": "string"
          },
          "secretKey": {
            "type": "string"
          }
        },
        "required": [
          "appId",
          "publicKey",
          "secretKey",
          "rotatedAt",
          "previousKeyExpiresAt"
        ],
        "type": "object"
      },
      "VerifySessionResponse": {
        "properties": {
          "records": {
            "items": {
              "properties": {
                "authoritativeError": {
                  "type": "string"
                },
                "fqdn": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                },
                "outcome": {
                  "enum": [
                    "verified",
                    "propagating",
                    "absent",
                    "indeterminate",
                    "domain_not_found"
                  ],
                  "type": "string"
                },
                "present": {
                  "type": "boolean"
                },
                "type": {
                  "type": "string"
                }
              },
              "required": [
                "fqdn",
                "type",
                "present",
                "note",
                "outcome"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "verified": {
            "type": "boolean"
          }
        },
        "required": [
          "verified",
          "records"
        ],
        "type": "object"
      },
      "WebhookEndpointInput": {
        "properties": {
          "url": {
            "minLength": 1,
            "type": "string"
          }
        },
        "required": [
          "url"
        ],
        "type": "object"
      },
      "WebhookEndpointSecretResponse": {
        "properties": {
          "appId": {
            "type": "string"
          },
          "createdAt": {
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
            "type": "string"
          },
          "id": {
            "type": "string"
          },
          "secret": {
            "type": "string"
          },
          "url": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "appId",
          "url",
          "createdAt",
          "secret"
        ],
        "type": "object"
      },
      "WebhookEndpointSummary": {
        "properties": {
          "appId": {
            "type": "string"
          },
          "createdAt": {
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
            "type": "string"
          },
          "id": {
            "type": "string"
          },
          "url": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "appId",
          "url",
          "createdAt"
        ],
        "type": "object"
      }
    },
    "securitySchemes": {
      "oauth2": {
        "description": "An OAuth 2.1 access token minted by DoDomain's own authorization server, sent as `Authorization: Bearer <jwt>`. Team-scoped rather than app-scoped, so operations that act on one app take an `appId`. Access is limited to the scopes granted at consent; a missing scope answers 403 with `details.code: SCOPE_MISSING`. Public clients register dynamically (RFC 7591) and must use PKCE.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://app.dodomain.io/api/auth/oauth2/authorize",
            "refreshUrl": "https://app.dodomain.io/api/auth/oauth2/token",
            "scopes": {
              "apps:read": "List the apps in your team",
              "connections:read": "List verified domain connections",
              "connections:write": "Request re-verification of an existing connection",
              "domains:read": "Check which DNS provider manages a domain and how it can connect",
              "sessions:read": "Read the status of domain-connect sessions",
              "sessions:write": "Create domain-connect sessions and trigger DNS verification"
            },
            "tokenUrl": "https://app.dodomain.io/api/auth/oauth2/token"
          }
        },
        "type": "oauth2"
      },
      "secretKey": {
        "description": "An app's SECRET key, sent as `Authorization: Bearer dd_sk_...`. Server-to-server only — never ship it to a browser. The key is app-scoped: it identifies exactly one app, carries no scope grammar, and grants that app's full access. The publishable key (`pk_*`) is not a credential and authenticates nothing.",
        "scheme": "bearer",
        "type": "http"
      }
    }
  },
  "info": {
    "description": "The `/api/v1` surface integrators call to create domain-connect sessions, drive detection and DNS verification, and manage the resulting connections.\n\n**Generated, never hand-written.** This document is produced from the same zod schemas the handlers validate with (`packages/core/src/schemas.ts`); a CI gate regenerates it on every PR and fails on any diff with the committed file, so an endpoint and its description cannot drift apart.\n\n**Errors** all share one envelope: `{ error, message?, details? }`, where `error` is one value from a finite, published code vocabulary — branch on it, never on the message prose.\n\n**Rate limits** answer `429 rate_limited` with a `Retry-After` header (whole seconds) and `details.reason` distinguishing a per-minute request cap from a per-connection re-check cooldown.",
    "summary": "Connect your customers' custom domains — DNS records written and verified.",
    "title": "DoDomain API",
    "version": "v1"
  },
  "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
  "openapi": "3.1.0",
  "paths": {
    "/api/v1/apps": {
      "get": {
        "description": "A secret key sees exactly its OWN app — the key is app-scoped, and listing siblings would widen one leaked key into team-wide reconnaissance. An OAuth token with `apps:read` sees every app in the caller's team. Only the publishable key (`pk_*`) is ever returned; secret material is absent from this contract by construction.",
        "operationId": "listApps",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListAppsResponse"
                }
              }
            },
            "description": "Success."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          }
        },
        "security": [
          {
            "secretKey": []
          },
          {
            "oauth2": [
              "apps:read"
            ]
          }
        ],
        "summary": "List apps",
        "tags": [
          "Apps"
        ]
      }
    },
    "/api/v1/cf/callback": {
      "get": {
        "description": "Cloudflare redirects the end user's browser here after consent. Not called by integrators. The session is found by its single-use `state` (an unknown or replayed `state` simply redirects home); the code is exchanged, the records are written through the user's own grant, verified, and the connection finalized.",
        "operationId": "handleCloudflareCallback",
        "responses": {
          "302": {
            "description": "Redirect back to the hosted connect page with a `cf` notice — or to the app root when the `state` is unknown or replayed.",
            "headers": {
              "Location": {
                "description": "Where the browser is sent next.",
                "required": true,
                "schema": {
                  "format": "uri",
                  "type": "string"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          }
        },
        "security": [],
        "summary": "Cloudflare OAuth return URL (called by Cloudflare)",
        "tags": [
          "One-click flows"
        ]
      }
    },
    "/api/v1/connections": {
      "get": {
        "description": "Newest-first list of connections and their live DNS health. A secret key lists its own app's connections; an OAuth token with `connections:read` lists team-wide and may narrow with `appId`. Disconnected connections are excluded unless `includeDisconnected=true`. Page past the 100-row ceiling by passing the previous page's `nextCursor` as `cursor`; the cursor is opaque — never construct one.",
        "operationId": "listConnections",
        "parameters": [
          {
            "in": "query",
            "name": "appId",
            "required": false,
            "schema": {
              "minLength": 1,
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "domain",
            "required": false,
            "schema": {
              "minLength": 1,
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "minLength": 1,
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "includeDisconnected",
            "required": false,
            "schema": {
              "default": false,
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListConnectionsResponse"
                }
              }
            },
            "description": "Success."
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          }
        },
        "security": [
          {
            "secretKey": []
          },
          {
            "oauth2": [
              "connections:read"
            ]
          }
        ],
        "summary": "List connections",
        "tags": [
          "Connections"
        ]
      }
    },
    "/api/v1/connections/{connectionId}": {
      "delete": {
        "description": "Archives the connection and stops its continuous DNS monitoring, then emits one `connection.disconnected` webhook. It is not a row delete — delivery history and metering are preserved. Idempotent: a repeat returns the ORIGINAL `disconnectedAt` with `alreadyDisconnected: true` and emits nothing, so a caller's own retry loop can never double-emit. 200 rather than 204 precisely so those two fields survive.",
        "operationId": "disconnectConnection",
        "parameters": [
          {
            "description": "The connection id from `GET /api/v1/connections`, or the `connectionId` on a `connection.*` webhook payload.",
            "in": "path",
            "name": "connectionId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DisconnectConnectionResponse"
                }
              }
            },
            "description": "Success."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          }
        },
        "security": [
          {
            "secretKey": []
          },
          {
            "oauth2": [
              "connections:write"
            ]
          }
        ],
        "summary": "Disconnect a connection",
        "tags": [
          "Connections"
        ]
      },
      "get": {
        "description": "Re-reads a single connection's live DNS health by the `connectionId` every `connection.*` webhook payload carries — without listing and filtering client-side, which is impossible past the paging ceiling anyway. The body is byte-identical to one element of `GET /api/v1/connections`'s `connections[]`, so one parser serves both. Unlike the list's default, a DISCONNECTED connection is still returned: a caller naming an id already knows the row exists, and `disconnectedAt` is exactly what it came to read. The OAuth scope is `connections:read` — this reads, it never mutates.",
        "operationId": "getConnection",
        "parameters": [
          {
            "description": "The connection id from `GET /api/v1/connections`, or the `connectionId` on a `connection.*` webhook payload.",
            "in": "path",
            "name": "connectionId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectionSummary"
                }
              }
            },
            "description": "Success."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          }
        },
        "security": [
          {
            "secretKey": []
          },
          {
            "oauth2": [
              "connections:read"
            ]
          }
        ],
        "summary": "Read one connection",
        "tags": [
          "Connections"
        ]
      }
    },
    "/api/v1/connections/{connectionId}/reverify": {
      "post": {
        "description": "Enqueues the same per-connection check the periodic monitor runs, so drift/recovery webhooks fire minutes after a support ticket instead of at the next tick. 202, not 200: the check runs in a worker and the result arrives as a `connection.verified` / `connection.failed` webhook, never in this response. `accepted: true` is deliberate rather than a job id — the check may coalesce with an already-queued job for the same connection.",
        "operationId": "reverifyConnection",
        "parameters": [
          {
            "description": "The connection id from `GET /api/v1/connections`, or the `connectionId` on a `connection.*` webhook payload.",
            "in": "path",
            "name": "connectionId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReverifyConnectionResponse"
                }
              }
            },
            "description": "Success."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          }
        },
        "security": [
          {
            "secretKey": []
          },
          {
            "oauth2": [
              "connections:write"
            ]
          }
        ],
        "summary": "Queue an on-demand DNS recheck",
        "tags": [
          "Connections"
        ]
      }
    },
    "/api/v1/domains/check": {
      "post": {
        "description": "Stateless pre-flight: which provider hosts the domain, which zone owns its records, which connect tier and flow it will get, and the manual guide if it comes to that — without creating a session. Nothing is persisted. Available on every plan.",
        "operationId": "checkDomain",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CheckDomainInput"
              }
            }
          },
          "description": "The domain to inspect.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckDomainResponse"
                }
              }
            },
            "description": "Success."
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          }
        },
        "security": [
          {
            "secretKey": []
          },
          {
            "oauth2": [
              "domains:read"
            ]
          }
        ],
        "summary": "Pre-flight a domain",
        "tags": [
          "Domains"
        ]
      }
    },
    "/api/v1/keys/rotate": {
      "post": {
        "description": "The calling app's secret key rotates ITSELF, and the new `dd_sk_...` is returned **once** — this is what makes scheduled credential rotation automatable instead of a dashboard click.\n\n**SELF-rotation only, deliberately.** There is no create, list, or revoke-another-key on this API: key inventory stays behind a human dashboard session. A stolen `dd_sk_` can already do everything the app can do until it is rotated, but it must not be able to mint a second, hidden credential that survives the owner rotating the one they know about. Rotating is, in effect, the revoke.\n\n**DEFAULT: no grace window.** With no body (or `overlapHours: 0`) the previous key stops authenticating the instant this returns, so **this response is the only copy of the new key** — a caller that drops it has locked itself out of the API and must rotate again from the dashboard. A zero-overlap rotate also **terminates any overlap window still live** from an earlier rotation; rotating with the default is how you revoke a previous key early.\n\n**OPT-IN OVERLAP.** `{\"overlapHours\": 1}` or `{\"overlapHours\": 24}` keeps the old key authenticating alongside the new one until `previousKeyExpiresAt`, so a rotator can deploy the new key with zero downtime. **Exactly one previous key is ever kept:** rotating again overwrites the slot and key n-1 dies instantly, regardless of remaining window. `publicKey` is echoed unchanged (it identifies the app in the widget and is never rotated here) so a CI job can assert it rewrote the right app.",
        "operationId": "rotateAppSecretKey",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RotateAppSecretKeyInput"
              }
            }
          },
          "description": "Optional. Absent (the pre-window contract) or `overlapHours: 0` = immediate cutover; 1 | 24 = the overlap window in hours.",
          "required": false
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RotateAppSecretKeyResponse"
                }
              }
            },
            "description": "Success."
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          }
        },
        "security": [
          {
            "secretKey": []
          }
        ],
        "summary": "Rotate the calling app's secret key",
        "tags": [
          "Credentials"
        ]
      }
    },
    "/api/v1/sessions": {
      "post": {
        "description": "Mints a short-lived (24h) session for one domain and the DNS records the end user must create, and returns the hosted `connectUrl` plus the fully-qualified names verification will look up. Hosts are composed relative to the session's `domain` — read `records[].fqdn` in the response to confirm what will actually be checked. An OAuth caller is team-scoped and must send `appId`; a secret-key caller's app is implicit in the key and a mismatching `appId` is rejected.",
        "operationId": "createSession",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSessionInput"
              }
            }
          },
          "description": "The domain plus the records to connect.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateSessionResponse"
                }
              }
            },
            "description": "Success."
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/QuotaExceeded"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          }
        },
        "security": [
          {
            "secretKey": []
          },
          {
            "oauth2": [
              "sessions:write"
            ]
          }
        ],
        "summary": "Create a connect session",
        "tags": [
          "Sessions"
        ]
      }
    },
    "/api/v1/sessions/{token}": {
      "get": {
        "description": "ONE path with TWO arms, discriminated by the SHAPE of the path segment — never by which credential you send.\n\n**Token arm** — pass the `dd_sess_...` token and send no `Authorization` header. Public: the token is the capability. Returns `PublicSession`: the records exactly as you requested them, plus status, detected provider and expiry. Once the session's 24 hours are up this arm answers `410 expired` forever, which is correct for a capability URL and useless for observability — hence the other arm.\n\n**Id arm** — pass the session id and authenticate with a `dd_sk_` secret key or an OAuth token carrying `sessions:read`. Returns `IntegratorSession`, and it can do two things the token arm cannot: it is addressable by the `sessionId` that every webhook payload carries (the token never appears in one), and **it reads an EXPIRED session** rather than refusing it — reporting `expired: true` instead of `410`. That flag is DERIVED at read (`expiresAt <= now`), so it is already true in the window before the reaper persists `status: \"expired\"`; do not infer expiry from `status` alone. Its `records` are the COMPOSED names (`type`/`host`/`fqdn`) actually looked up on DNS, not the raw request echo the token arm returns.\n\n`tier` and `detectedProvider` are `null` on both arms until detection runs — that pair is the 'detection has not run yet' signal, never a detection failure.",
        "operationId": "getSession",
        "parameters": [
          {
            "description": "Either a session token (`dd_sess_...`) or a session id (the `sessionId` every webhook payload carries). The handler discriminates on the value's shape, not on your credential: a token makes this the PUBLIC arm (the token itself is the capability — treat it like a bearer token), an id makes it the integrator-authed arm.",
            "in": "path",
            "name": "token",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/PublicSession"
                    },
                    {
                      "$ref": "#/components/schemas/IntegratorSession"
                    }
                  ]
                }
              }
            },
            "description": "`PublicSession` when addressed by token, `IntegratorSession` when addressed by id. The two are told apart by their fields: only `IntegratorSession` carries `appId`, `connectionId`, `createdAt` and `expired`."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "410": {
            "$ref": "#/components/responses/Expired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          }
        },
        "security": [
          {},
          {
            "secretKey": []
          },
          {
            "oauth2": [
              "sessions:read"
            ]
          }
        ],
        "summary": "Read a connect session (by token, or by id with a credential)",
        "tags": [
          "Sessions"
        ]
      }
    },
    "/api/v1/sessions/{token}/cloudflare/start": {
      "get": {
        "description": "Browser entry point for the tier-1 Cloudflare flow: mints a PKCE pair and a CSRF `state`, binds them to the session, and redirects the end user to Cloudflare's consent page. Navigate to it (a link or a redirect) — do not fetch it from script.",
        "operationId": "startCloudflareAuthorization",
        "parameters": [
          {
            "description": "The session token returned by `POST /api/v1/sessions` (`dd_sess_...`). This value IS the credential for the operation — treat it like a bearer token and never log or share it.",
            "in": "path",
            "name": "token",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "302": {
            "description": "Redirect to Cloudflare's OAuth consent page.",
            "headers": {
              "Location": {
                "description": "Where the browser is sent next.",
                "required": true,
                "schema": {
                  "format": "uri",
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "410": {
            "$ref": "#/components/responses/Expired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/NotConfigured"
          }
        },
        "security": [],
        "summary": "Start the Cloudflare one-click flow",
        "tags": [
          "One-click flows"
        ]
      }
    },
    "/api/v1/sessions/{token}/detect": {
      "post": {
        "description": "Runs provider detection for the session's domain and persists the resulting tier on the session. `zone` is the zone that OWNS the records (the registrable apex, or a delegated subzone when one exists) — that is the zone the end user opens at their provider, which is why the returned `guide` is written against it rather than against the subdomain being connected.",
        "operationId": "detectSessionProvider",
        "parameters": [
          {
            "description": "The session token returned by `POST /api/v1/sessions` (`dd_sess_...`). This value IS the credential for the operation — treat it like a bearer token and never log or share it.",
            "in": "path",
            "name": "token",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DetectSessionResponse"
                }
              }
            },
            "description": "Success."
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "410": {
            "$ref": "#/components/responses/Expired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          }
        },
        "security": [],
        "summary": "Detect the session domain's DNS provider",
        "tags": [
          "Sessions"
        ]
      }
    },
    "/api/v1/sessions/{token}/domain-connect/callback": {
      "get": {
        "description": "The DNS provider redirects the end user's browser here after the apply page. Not called by integrators. The single-use `state` minted at start is re-derived and mismatches are refused before anything is written; the session's records are then re-verified against authoritative DNS, and the connection is finalized only when every record is live.",
        "operationId": "handleDomainConnectCallback",
        "parameters": [
          {
            "description": "The session token returned by `POST /api/v1/sessions` (`dd_sess_...`). This value IS the credential for the operation — treat it like a bearer token and never log or share it.",
            "in": "path",
            "name": "token",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "302": {
            "description": "Redirect back to the hosted connect page, carrying a `dc` notice describing the outcome.",
            "headers": {
              "Location": {
                "description": "Where the browser is sent next.",
                "required": true,
                "schema": {
                  "format": "uri",
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "410": {
            "$ref": "#/components/responses/Expired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          }
        },
        "security": [],
        "summary": "Domain Connect return URL (called by the DNS provider)",
        "tags": [
          "One-click flows"
        ]
      }
    },
    "/api/v1/sessions/{token}/domain-connect/start": {
      "get": {
        "description": "Browser entry point for the tier-2 Domain Connect flow: compiles the session's records into a published template, discovers the owning zone's DNS provider, confirms the provider has onboarded that template, and redirects the end user to the provider's own consent/apply page. A redirect back is never treated as proof of success — the callback re-verifies against authoritative DNS. Navigate to it — do not fetch it from script.",
        "operationId": "startDomainConnectApply",
        "parameters": [
          {
            "description": "The session token returned by `POST /api/v1/sessions` (`dd_sess_...`). This value IS the credential for the operation — treat it like a bearer token and never log or share it.",
            "in": "path",
            "name": "token",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "302": {
            "description": "Redirect to the DNS provider's Domain Connect apply page — or, when the provider has not onboarded the template (or discovery fails), back to the hosted connect page carrying a `dc` notice so the end user can fall back to the manual records.",
            "headers": {
              "Location": {
                "description": "Where the browser is sent next.",
                "required": true,
                "schema": {
                  "format": "uri",
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "410": {
            "$ref": "#/components/responses/Expired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          },
          "503": {
            "$ref": "#/components/responses/NotConfigured"
          }
        },
        "security": [],
        "summary": "Start the Domain Connect one-click flow",
        "tags": [
          "One-click flows"
        ]
      }
    },
    "/api/v1/sessions/{token}/verify": {
      "post": {
        "description": "Checks every expected record against authoritative nameservers. When all are present the session is marked verified, the connection is recorded, usage is metered and webhooks fire — all in one transaction, and only on an actual authoritative match. `records[].outcome` distinguishes a genuine absence (`absent`), a still-propagating record (`propagating`), a check that could not be completed (`indeterminate`) and a domain that does not resolve at all (`domain_not_found`).",
        "operationId": "verifySession",
        "parameters": [
          {
            "description": "The session token returned by `POST /api/v1/sessions` (`dd_sess_...`). This value IS the credential for the operation — treat it like a bearer token and never log or share it.",
            "in": "path",
            "name": "token",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerifySessionResponse"
                }
              }
            },
            "description": "Success."
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "410": {
            "$ref": "#/components/responses/Expired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          }
        },
        "security": [],
        "summary": "Verify the session's records against live DNS",
        "tags": [
          "Sessions"
        ]
      }
    },
    "/api/v1/webhook-endpoints": {
      "get": {
        "description": "The app's delivery targets. NEVER returns `secret`: the signing secret is show-once at create and rotate, so a leaked read cannot recover the ability to forge our signatures. The app is implicit in the key, and that is also the authorization — a key only ever sees its OWN app's endpoints.",
        "operationId": "listWebhookEndpoints",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListWebhookEndpointsResponse"
                }
              }
            },
            "description": "Success."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          }
        },
        "security": [
          {
            "secretKey": []
          }
        ],
        "summary": "List webhook endpoints",
        "tags": [
          "Webhook endpoints"
        ]
      },
      "post": {
        "description": "Registers one delivery target and returns its signing secret **once**. `secret` (`whsec_...`) appears in this response and in a rotate response, and in no read surface ever again — store it now. The URL must be https and must not resolve to a localhost/private/link-local address, and an app cannot register the same normalized URL twice: both refusals are `400 invalid_request` with the reason in `message`. At the plan's endpoint cap it is `402 quota_exceeded`.",
        "operationId": "createWebhookEndpoint",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEndpointInput"
              }
            }
          },
          "description": "The https URL to deliver to.",
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpointSecretResponse"
                }
              }
            },
            "description": "Success."
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/QuotaExceeded"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          }
        },
        "security": [
          {
            "secretKey": []
          }
        ],
        "summary": "Register a webhook endpoint",
        "tags": [
          "Webhook endpoints"
        ]
      }
    },
    "/api/v1/webhook-endpoints/{endpointId}": {
      "delete": {
        "description": "Stops delivering to the endpoint. Its past delivery rows survive as evidence, but a failed delivery to a deleted endpoint can no longer be redriven — the same irreversible outcome the dashboard's confirm dialog spells out. 200 with a body rather than 204, matching `DELETE /api/v1/connections/{connectionId}`, so an automated caller can log WHAT it removed.",
        "operationId": "deleteWebhookEndpoint",
        "parameters": [
          {
            "description": "The endpoint id from `GET /api/v1/webhook-endpoints`.",
            "in": "path",
            "name": "endpointId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteWebhookEndpointResponse"
                }
              }
            },
            "description": "Success."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          }
        },
        "security": [
          {
            "secretKey": []
          }
        ],
        "summary": "Delete a webhook endpoint",
        "tags": [
          "Webhook endpoints"
        ]
      },
      "patch": {
        "description": "Points an existing endpoint at a new URL. PATCH rather than PUT because `url` is the only mutable field, so the body is a partial by construction. The signing secret is deliberately UNTOUCHED — moving hosts must not force a receiver to re-key; rotating is the explicit sibling call. Same URL policy and same duplicate rule as create; the plan cap is NOT re-checked, because an update never grows the set and an app already over a lowered cap must still be able to fix a wrong URL. An unknown id and another app's id both answer 404.",
        "operationId": "updateWebhookEndpoint",
        "parameters": [
          {
            "description": "The endpoint id from `GET /api/v1/webhook-endpoints`.",
            "in": "path",
            "name": "endpointId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEndpointInput"
              }
            }
          },
          "description": "The new https URL.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpointSummary"
                }
              }
            },
            "description": "Success."
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          }
        },
        "security": [
          {
            "secretKey": []
          }
        ],
        "summary": "Repoint a webhook endpoint",
        "tags": [
          "Webhook endpoints"
        ]
      }
    },
    "/api/v1/webhook-endpoints/{endpointId}/rotate-secret": {
      "post": {
        "description": "Mints a new signing secret and returns it **once**. A verb sub-path rather than a field on PATCH — mirroring `POST /api/v1/connections/{connectionId}/reverify` — because rotation is an ACTION with a once-only result, not a property you can idempotently PUT; that also keeps the show-once secret out of every ordinary update response. **IMMEDIATE CUTOVER, no dual-secret window:** the worker reads the secret live at delivery time, so signatures switch at once, including retries of deliveries created before the rotation. Deploy the new secret to your receiver promptly.",
        "operationId": "rotateWebhookEndpointSecret",
        "parameters": [
          {
            "description": "The endpoint id from `GET /api/v1/webhook-endpoints`.",
            "in": "path",
            "name": "endpointId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpointSecretResponse"
                }
              }
            },
            "description": "Success."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/Internal"
          }
        },
        "security": [
          {
            "secretKey": []
          }
        ],
        "summary": "Rotate a webhook endpoint's signing secret",
        "tags": [
          "Webhook endpoints"
        ]
      }
    }
  },
  "servers": [
    {
      "description": "Production",
      "url": "https://app.dodomain.io"
    }
  ],
  "tags": [
    {
      "description": "Create a connect session, read its state, and drive detection and DNS verification for it.",
      "name": "Sessions"
    },
    {
      "description": "Browser-navigated entry points and provider return URLs for the Cloudflare (tier 1) and Domain Connect (tier 2) one-click paths.",
      "name": "One-click flows"
    },
    {
      "description": "Stateless domain pre-flight — no session required.",
      "name": "Domains"
    },
    {
      "description": "The apps in your team and their publishable keys.",
      "name": "Apps"
    },
    {
      "description": "Verified connections, their live DNS health, re-verification and disconnection.",
      "name": "Connections"
    },
    {
      "description": "Where DoDomain delivers your app's events, and the signing secrets those deliveries are signed with.",
      "name": "Webhook endpoints"
    },
    {
      "description": "Rotation of an app's own secret key. These operations accept the secret key ONLY — an OAuth access token is refused.",
      "name": "Credentials"
    }
  ]
}
