{
  "components": {
    "schemas": {
      "Budget": {
        "additionalProperties": false,
        "properties": {
          "amount": {
            "type": "string"
          },
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "created_by": {
            "type": "string"
          },
          "currency": {
            "type": "string"
          },
          "custom_period": {
            "$ref": "#/components/schemas/TimePeriod"
          },
          "enabled": {
            "type": "boolean"
          },
          "filter": {
            "$ref": "#/components/schemas/Filter"
          },
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "period": {
            "type": "string"
          },
          "project_id": {
            "type": "string"
          },
          "revision": {
            "format": "int64",
            "type": "integer"
          },
          "status": {
            "$ref": "#/components/schemas/Status",
            "description": "Spend and threshold states for the current window as of its latest check, or null before the window's first check."
          },
          "thresholds": {
            "items": {
              "$ref": "#/components/schemas/Threshold"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "updated_at": {
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "project_id",
          "enabled",
          "amount",
          "currency",
          "period",
          "filter",
          "thresholds",
          "revision",
          "created_by",
          "created_at",
          "updated_at",
          "status"
        ],
        "type": "object"
      },
      "BudgetSpec": {
        "additionalProperties": false,
        "properties": {
          "amount": {
            "description": "Budgeted amount per window, as a decimal string.",
            "pattern": "^[0-9]+(\\.[0-9]+)?$",
            "type": "string"
          },
          "currency": {
            "description": "Defaults to, and must equal, the currency your costs are reported in.",
            "type": "string"
          },
          "custom_period": {
            "$ref": "#/components/schemas/TimePeriod",
            "description": "Required when period is custom, at most 366 days."
          },
          "enabled": {
            "description": "Defaults to true. A disabled budget is not checked and sends no alerts.",
            "type": "boolean"
          },
          "filter": {
            "$ref": "#/components/schemas/Filter"
          },
          "name": {
            "maxLength": 64,
            "minLength": 1,
            "type": "string"
          },
          "period": {
            "description": "Calendar periods reset in UTC; custom is one fixed window.",
            "enum": [
              "daily",
              "monthly",
              "quarterly",
              "yearly",
              "custom"
            ],
            "type": "string"
          },
          "project_id": {
            "description": "The project the budget belongs to and measures. Omitted, it is the caller's own project.",
            "maxLength": 255,
            "type": "string"
          },
          "thresholds": {
            "items": {
              "$ref": "#/components/schemas/ThresholdSpec"
            },
            "maxItems": 10,
            "minItems": 1,
            "type": [
              "array",
              "null"
            ]
          }
        },
        "required": [
          "name",
          "amount",
          "period",
          "thresholds"
        ],
        "type": "object"
      },
      "Channel": {
        "additionalProperties": false,
        "properties": {
          "address": {
            "type": "string"
          },
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "created_by": {
            "type": "string"
          },
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "project_id": {
            "type": "string"
          },
          "secret": {
            "description": "A webhook's signing secret. It is returned only when the channel is created and when the secret is rotated.",
            "type": "string"
          },
          "status": {
            "enum": [
              "pending_confirmation",
              "confirmed"
            ],
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "url": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "project_id",
          "type",
          "status",
          "created_by",
          "created_at"
        ],
        "type": "object"
      },
      "ChannelSpec": {
        "additionalProperties": false,
        "properties": {
          "address": {
            "description": "For an email channel: the address. It receives nothing until its owner follows the confirmation link sent to it.",
            "maxLength": 254,
            "type": "string"
          },
          "name": {
            "maxLength": 64,
            "minLength": 1,
            "type": "string"
          },
          "project_id": {
            "description": "The owning project. Omitted, it is the caller's own project.",
            "maxLength": 255,
            "type": "string"
          },
          "type": {
            "enum": [
              "email",
              "webhook"
            ],
            "type": "string"
          },
          "url": {
            "description": "For a webhook channel: an https URL on a public address. Deliveries are signed; see X-Budget-Signature.",
            "maxLength": 2048,
            "type": "string"
          }
        },
        "required": [
          "name",
          "type"
        ],
        "type": "object"
      },
      "ChannelsOutputBody": {
        "additionalProperties": false,
        "properties": {
          "channels": {
            "items": {
              "$ref": "#/components/schemas/Channel"
            },
            "type": [
              "array",
              "null"
            ]
          }
        },
        "required": [
          "channels"
        ],
        "type": "object"
      },
      "ErrorDetail": {
        "additionalProperties": false,
        "properties": {
          "location": {
            "description": "Where the error occurred, e.g. 'body.items[3].tags' or 'path.thing-id'",
            "type": "string"
          },
          "message": {
            "description": "Error message text",
            "type": "string"
          },
          "value": {
            "description": "The value at the given location"
          }
        },
        "type": "object"
      },
      "ErrorModel": {
        "additionalProperties": false,
        "properties": {
          "detail": {
            "description": "A human-readable explanation specific to this occurrence of the problem.",
            "examples": [
              "Property foo is required but is missing."
            ],
            "type": "string"
          },
          "errors": {
            "description": "Optional list of individual error details",
            "items": {
              "$ref": "#/components/schemas/ErrorDetail"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "instance": {
            "description": "A URI reference that identifies the specific occurrence of the problem.",
            "examples": [
              "https://example.com/error-log/abc123"
            ],
            "format": "uri",
            "type": "string"
          },
          "status": {
            "description": "HTTP status code",
            "examples": [
              400
            ],
            "format": "int64",
            "type": "integer"
          },
          "title": {
            "description": "A short, human-readable summary of the problem type. This value should not change between occurrences of the error.",
            "examples": [
              "Bad Request"
            ],
            "type": "string"
          },
          "type": {
            "default": "about:blank",
            "description": "A URI reference to human-readable documentation for the error.",
            "examples": [
              "https://example.com/errors/example"
            ],
            "format": "uri",
            "type": "string"
          }
        },
        "type": "object"
      },
      "Filter": {
        "additionalProperties": false,
        "properties": {
          "regions": {
            "description": "Regions to count. Empty counts every region.",
            "items": {
              "type": "string"
            },
            "maxItems": 100,
            "type": [
              "array",
              "null"
            ]
          },
          "services": {
            "description": "Services to count. Empty counts every service.",
            "items": {
              "enum": [
                "compute",
                "image",
                "volume"
              ],
              "type": "string"
            },
            "maxItems": 3,
            "type": [
              "array",
              "null"
            ]
          },
          "usage_types": {
            "description": "Usage types to count. Empty counts every usage type.",
            "items": {
              "type": "string"
            },
            "maxItems": 100,
            "type": [
              "array",
              "null"
            ]
          }
        },
        "type": "object"
      },
      "ListOutputBody": {
        "additionalProperties": false,
        "properties": {
          "budgets": {
            "items": {
              "$ref": "#/components/schemas/Budget"
            },
            "type": [
              "array",
              "null"
            ]
          }
        },
        "required": [
          "budgets"
        ],
        "type": "object"
      },
      "Notification": {
        "additionalProperties": false,
        "properties": {
          "attempts": {
            "format": "int64",
            "type": "integer"
          },
          "channel_id": {
            "type": "string"
          },
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "id": {
            "format": "int64",
            "type": "integer"
          },
          "last_error": {
            "type": "string"
          },
          "observed": {
            "type": "string"
          },
          "sent_at": {
            "format": "date-time",
            "type": "string"
          },
          "status": {
            "enum": [
              "pending",
              "sent",
              "failed"
            ],
            "type": "string"
          },
          "target_amount": {
            "type": "string"
          },
          "threshold": {
            "type": "string"
          },
          "time_period": {
            "$ref": "#/components/schemas/TimePeriod"
          }
        },
        "required": [
          "id",
          "threshold",
          "channel_id",
          "time_period",
          "target_amount",
          "observed",
          "status",
          "attempts",
          "created_at"
        ],
        "type": "object"
      },
      "NotificationsOutputBody": {
        "additionalProperties": false,
        "properties": {
          "notifications": {
            "items": {
              "$ref": "#/components/schemas/Notification"
            },
            "type": [
              "array",
              "null"
            ]
          }
        },
        "required": [
          "notifications"
        ],
        "type": "object"
      },
      "Status": {
        "additionalProperties": false,
        "properties": {
          "actual_spend": {
            "type": "string"
          },
          "amount": {
            "description": "The budget amount the thresholds were judged against.",
            "type": "string"
          },
          "as_of": {
            "description": "When the spend was measured.",
            "format": "date-time",
            "type": "string"
          },
          "currency": {
            "type": "string"
          },
          "data_through": {
            "description": "How far the usage data is complete. Spend after this is not counted yet.",
            "format": "date-time",
            "type": "string"
          },
          "estimated": {
            "description": "Always true: costs are computed from usage and a price catalogue.",
            "type": "boolean"
          },
          "evaluated_at": {
            "format": "date-time",
            "type": "string"
          },
          "forecast_spend": {
            "description": "Linear projection for the whole window; null until enough of it has elapsed.",
            "type": [
              "string",
              "null"
            ]
          },
          "thresholds": {
            "items": {
              "$ref": "#/components/schemas/ThresholdStatus"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "time_period": {
            "$ref": "#/components/schemas/TimePeriod"
          }
        },
        "required": [
          "time_period",
          "evaluated_at",
          "as_of",
          "data_through",
          "currency",
          "amount",
          "actual_spend",
          "forecast_spend",
          "thresholds",
          "estimated"
        ],
        "type": "object"
      },
      "Threshold": {
        "additionalProperties": false,
        "properties": {
          "basis": {
            "type": "string"
          },
          "channels": {
            "items": {
              "type": "string"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "comparison": {
            "type": "string"
          },
          "enabled": {
            "type": "boolean"
          },
          "message": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "unit": {
            "type": "string"
          },
          "value": {
            "type": "string"
          }
        },
        "required": [
          "name",
          "basis",
          "unit",
          "value",
          "comparison",
          "enabled",
          "channels"
        ],
        "type": "object"
      },
      "ThresholdSpec": {
        "additionalProperties": false,
        "properties": {
          "basis": {
            "description": "Judge spend so far (actual) or the projection for the whole window (forecasted).",
            "enum": [
              "actual",
              "forecasted"
            ],
            "type": "string"
          },
          "channels": {
            "description": "Configured notification channels to alert when the threshold enters alarm. With none the threshold still tracks its state.",
            "items": {
              "type": "string"
            },
            "maxItems": 10,
            "type": [
              "array",
              "null"
            ]
          },
          "comparison": {
            "description": "Defaults to greater_than_or_equal.",
            "enum": [
              "greater_than",
              "greater_than_or_equal",
              "less_than",
              "less_than_or_equal"
            ],
            "type": "string"
          },
          "enabled": {
            "description": "Defaults to true.",
            "type": "boolean"
          },
          "message": {
            "description": "Text included in the alert.",
            "maxLength": 1000,
            "type": "string"
          },
          "name": {
            "description": "Unique within the budget. State is tracked by name, so renaming a threshold restarts it.",
            "maxLength": 64,
            "minLength": 1,
            "type": "string"
          },
          "unit": {
            "description": "Whether value is a percentage of the budget amount or an amount in its currency.",
            "enum": [
              "percentage",
              "absolute"
            ],
            "type": "string"
          },
          "value": {
            "description": "Decimal string.",
            "pattern": "^[0-9]+(\\.[0-9]+)?$",
            "type": "string"
          }
        },
        "required": [
          "name",
          "basis",
          "unit",
          "value"
        ],
        "type": "object"
      },
      "ThresholdStatus": {
        "additionalProperties": false,
        "properties": {
          "changed_at": {
            "description": "When the threshold last entered or left alarm in this window.",
            "format": "date-time",
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "observed": {
            "type": [
              "string",
              "null"
            ]
          },
          "state": {
            "enum": [
              "ok",
              "alarm",
              "unevaluated",
              "disabled"
            ],
            "type": "string"
          },
          "target_amount": {
            "type": "string"
          }
        },
        "required": [
          "name",
          "state",
          "target_amount",
          "observed"
        ],
        "type": "object"
      },
      "TimePeriod": {
        "additionalProperties": false,
        "properties": {
          "end": {
            "description": "Exclusive end (RFC 3339).",
            "format": "date-time",
            "type": "string"
          },
          "start": {
            "description": "Inclusive start (RFC 3339).",
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "start",
          "end"
        ],
        "type": "object"
      },
      "UpdateSpec": {
        "additionalProperties": false,
        "properties": {
          "amount": {
            "description": "Budgeted amount per window, as a decimal string.",
            "pattern": "^[0-9]+(\\.[0-9]+)?$",
            "type": "string"
          },
          "currency": {
            "description": "Defaults to, and must equal, the currency your costs are reported in.",
            "type": "string"
          },
          "custom_period": {
            "$ref": "#/components/schemas/TimePeriod",
            "description": "Required when period is custom, at most 366 days."
          },
          "enabled": {
            "description": "Defaults to true. A disabled budget is not checked and sends no alerts.",
            "type": "boolean"
          },
          "filter": {
            "$ref": "#/components/schemas/Filter"
          },
          "name": {
            "maxLength": 64,
            "minLength": 1,
            "type": "string"
          },
          "period": {
            "description": "Calendar periods reset in UTC; custom is one fixed window.",
            "enum": [
              "daily",
              "monthly",
              "quarterly",
              "yearly",
              "custom"
            ],
            "type": "string"
          },
          "project_id": {
            "description": "The project the budget belongs to and measures. Omitted, it is the caller's own project.",
            "maxLength": 255,
            "type": "string"
          },
          "revision": {
            "description": "The revision the budget was read at. A stale revision is refused with 409.",
            "format": "int64",
            "minimum": 1,
            "type": "integer"
          },
          "thresholds": {
            "items": {
              "$ref": "#/components/schemas/ThresholdSpec"
            },
            "maxItems": 10,
            "minItems": 1,
            "type": [
              "array",
              "null"
            ]
          }
        },
        "required": [
          "revision",
          "name",
          "amount",
          "period",
          "thresholds"
        ],
        "type": "object"
      },
      "WindowsOutputBody": {
        "additionalProperties": false,
        "properties": {
          "windows": {
            "items": {
              "$ref": "#/components/schemas/Status"
            },
            "type": [
              "array",
              "null"
            ]
          }
        },
        "required": [
          "windows"
        ],
        "type": "object"
      }
    },
    "securitySchemes": {
      "keystone": {
        "in": "header",
        "name": "X-Auth-Token",
        "type": "apiKey"
      }
    }
  },
  "info": {
    "description": "Spending limits for projects, with alerts by email or webhook when spend or its forecast crosses a threshold.",
    "title": "Budget",
    "version": "0.0.0-dev"
  },
  "openapi": "3.1.0",
  "paths": {
    "/v1/budgets": {
      "get": {
        "description": "Lists the budgets of your token's project, each with the status of its current window.",
        "operationId": "list_budgets",
        "parameters": [
          {
            "description": "The project whose budgets to list. Defaults to your token's project; naming another project is refused with 403.",
            "explode": false,
            "in": "query",
            "name": "project_id",
            "schema": {
              "description": "The project whose budgets to list. Defaults to your token's project; naming another project is refused with 403.",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListOutputBody"
                }
              }
            },
            "description": "OK"
          },
          "400": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Bad Request"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Unauthorized"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Forbidden"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Not Found"
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Conflict"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Unprocessable Entity"
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Internal Server Error"
          },
          "503": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Service Unavailable"
          }
        },
        "summary": "List budgets",
        "tags": [
          "Budgets"
        ]
      },
      "post": {
        "operationId": "create_budget",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BudgetSpec"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Budget"
                }
              }
            },
            "description": "Created"
          },
          "400": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Bad Request"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Unauthorized"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Forbidden"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Not Found"
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Conflict"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Unprocessable Entity"
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Internal Server Error"
          },
          "503": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Service Unavailable"
          }
        },
        "summary": "Create a budget",
        "tags": [
          "Budgets"
        ]
      }
    },
    "/v1/budgets/{budget_id}": {
      "delete": {
        "operationId": "delete_budget",
        "parameters": [
          {
            "description": "Budget identifier.",
            "in": "path",
            "name": "budget_id",
            "required": true,
            "schema": {
              "description": "Budget identifier.",
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content"
          },
          "400": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Bad Request"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Unauthorized"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Forbidden"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Not Found"
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Conflict"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Unprocessable Entity"
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Internal Server Error"
          },
          "503": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Service Unavailable"
          }
        },
        "summary": "Delete a budget and its history",
        "tags": [
          "Budgets"
        ]
      },
      "get": {
        "operationId": "get_budget",
        "parameters": [
          {
            "description": "Budget identifier.",
            "in": "path",
            "name": "budget_id",
            "required": true,
            "schema": {
              "description": "Budget identifier.",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Budget"
                }
              }
            },
            "description": "OK"
          },
          "400": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Bad Request"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Unauthorized"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Forbidden"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Not Found"
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Conflict"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Unprocessable Entity"
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Internal Server Error"
          },
          "503": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Service Unavailable"
          }
        },
        "summary": "Get a budget and the status of its current window",
        "tags": [
          "Budgets"
        ]
      },
      "put": {
        "description": "The body carries the revision the budget was read at; a stale one is refused with 409.",
        "operationId": "update_budget",
        "parameters": [
          {
            "description": "Budget identifier.",
            "in": "path",
            "name": "budget_id",
            "required": true,
            "schema": {
              "description": "Budget identifier.",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateSpec"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Budget"
                }
              }
            },
            "description": "OK"
          },
          "400": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Bad Request"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Unauthorized"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Forbidden"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Not Found"
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Conflict"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Unprocessable Entity"
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Internal Server Error"
          },
          "503": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Service Unavailable"
          }
        },
        "summary": "Replace a budget",
        "tags": [
          "Budgets"
        ]
      }
    },
    "/v1/budgets/{budget_id}/notifications": {
      "get": {
        "operationId": "list_budget_notifications",
        "parameters": [
          {
            "description": "Budget identifier.",
            "in": "path",
            "name": "budget_id",
            "required": true,
            "schema": {
              "description": "Budget identifier.",
              "type": "string"
            }
          },
          {
            "explode": false,
            "in": "query",
            "name": "limit",
            "schema": {
              "default": 50,
              "format": "int64",
              "maximum": 500,
              "minimum": 1,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotificationsOutputBody"
                }
              }
            },
            "description": "OK"
          },
          "400": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Bad Request"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Unauthorized"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Forbidden"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Not Found"
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Conflict"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Unprocessable Entity"
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Internal Server Error"
          },
          "503": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Service Unavailable"
          }
        },
        "summary": "List a budget's alerts and their delivery, newest first",
        "tags": [
          "Budgets"
        ]
      }
    },
    "/v1/budgets/{budget_id}/windows": {
      "get": {
        "operationId": "list_budget_windows",
        "parameters": [
          {
            "description": "Budget identifier.",
            "in": "path",
            "name": "budget_id",
            "required": true,
            "schema": {
              "description": "Budget identifier.",
              "type": "string"
            }
          },
          {
            "explode": false,
            "in": "query",
            "name": "limit",
            "schema": {
              "default": 50,
              "format": "int64",
              "maximum": 500,
              "minimum": 1,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WindowsOutputBody"
                }
              }
            },
            "description": "OK"
          },
          "400": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Bad Request"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Unauthorized"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Forbidden"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Not Found"
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Conflict"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Unprocessable Entity"
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Internal Server Error"
          },
          "503": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Service Unavailable"
          }
        },
        "summary": "List a budget's past and current windows, newest first",
        "tags": [
          "Budgets"
        ]
      }
    },
    "/v1/notification_channels": {
      "get": {
        "operationId": "list_notification_channels",
        "parameters": [
          {
            "description": "The project whose channels to list. Defaults to your token's project; naming another project is refused with 403.",
            "explode": false,
            "in": "query",
            "name": "project_id",
            "schema": {
              "description": "The project whose channels to list. Defaults to your token's project; naming another project is refused with 403.",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChannelsOutputBody"
                }
              }
            },
            "description": "OK"
          },
          "400": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Bad Request"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Unauthorized"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Forbidden"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Not Found"
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Conflict"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Unprocessable Entity"
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Internal Server Error"
          },
          "503": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Service Unavailable"
          }
        },
        "summary": "List notification channels",
        "tags": [
          "Notification channels"
        ]
      },
      "post": {
        "description": "An email channel is sent a confirmation link and receives nothing until it is followed. A webhook channel is usable at once; its signing secret is in this response only.",
        "operationId": "create_notification_channel",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChannelSpec"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Channel"
                }
              }
            },
            "description": "Created"
          },
          "400": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Bad Request"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Unauthorized"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Forbidden"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Not Found"
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Conflict"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Unprocessable Entity"
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Internal Server Error"
          },
          "503": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Service Unavailable"
          }
        },
        "summary": "Create a notification channel",
        "tags": [
          "Notification channels"
        ]
      }
    },
    "/v1/notification_channels/{channel_id}": {
      "delete": {
        "description": "Refused with 409 while a budget's threshold names it.",
        "operationId": "delete_notification_channel",
        "parameters": [
          {
            "description": "Notification channel identifier.",
            "in": "path",
            "name": "channel_id",
            "required": true,
            "schema": {
              "description": "Notification channel identifier.",
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content"
          },
          "400": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Bad Request"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Unauthorized"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Forbidden"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Not Found"
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Conflict"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Unprocessable Entity"
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Internal Server Error"
          },
          "503": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Service Unavailable"
          }
        },
        "summary": "Delete a notification channel",
        "tags": [
          "Notification channels"
        ]
      },
      "get": {
        "operationId": "get_notification_channel",
        "parameters": [
          {
            "description": "Notification channel identifier.",
            "in": "path",
            "name": "channel_id",
            "required": true,
            "schema": {
              "description": "Notification channel identifier.",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Channel"
                }
              }
            },
            "description": "OK"
          },
          "400": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Bad Request"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Unauthorized"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Forbidden"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Not Found"
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Conflict"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Unprocessable Entity"
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Internal Server Error"
          },
          "503": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Service Unavailable"
          }
        },
        "summary": "Get a notification channel",
        "tags": [
          "Notification channels"
        ]
      }
    },
    "/v1/notification_channels/{channel_id}/resend_confirmation": {
      "post": {
        "description": "At most one is sent every ten minutes.",
        "operationId": "resend_channel_confirmation",
        "parameters": [
          {
            "description": "Notification channel identifier.",
            "in": "path",
            "name": "channel_id",
            "required": true,
            "schema": {
              "description": "Notification channel identifier.",
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Accepted"
          },
          "400": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Bad Request"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Unauthorized"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Forbidden"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Not Found"
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Conflict"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Unprocessable Entity"
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Internal Server Error"
          },
          "503": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Service Unavailable"
          }
        },
        "summary": "Send an email channel's confirmation link again",
        "tags": [
          "Notification channels"
        ]
      }
    },
    "/v1/notification_channels/{channel_id}/rotate_secret": {
      "post": {
        "operationId": "rotate_channel_secret",
        "parameters": [
          {
            "description": "Notification channel identifier.",
            "in": "path",
            "name": "channel_id",
            "required": true,
            "schema": {
              "description": "Notification channel identifier.",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Channel"
                }
              }
            },
            "description": "OK"
          },
          "400": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Bad Request"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Unauthorized"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Forbidden"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Not Found"
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Conflict"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Unprocessable Entity"
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Internal Server Error"
          },
          "503": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorModel"
                }
              }
            },
            "description": "Service Unavailable"
          }
        },
        "summary": "Replace a webhook channel's signing secret",
        "tags": [
          "Notification channels"
        ]
      }
    }
  },
  "security": [
    {
      "keystone": []
    }
  ],
  "servers": [
    {
      "url": "https://budget.public.global.vexxhost.net"
    }
  ],
  "tags": [
    {
      "name": "Budgets"
    },
    {
      "name": "Notification channels"
    }
  ]
}