# Identity

> Identity is the sign-in service that issues the tokens every API, the OpenStack CLI and the OpenStack SDKs use.

Identity is where you sign in. It checks who you are and gives you a token, which
every other service accepts, so you never sign in to a service separately. Identity also lists
where each service is, in the service catalog.


Identity is OpenStack Keystone, so any tool that works with OpenStack can sign in with it at
`https://auth.vexxhost.net/v3`.


## Concepts

<Cards>
  <Card title="Authentication" href="/identity/authentication" description="How tokens prove who you are, what they are valid for, and when they expire." />

  <Card title="Projects" href="/identity/projects" description="How a token's project decides which resources you see and change." />

  <Card title="Service catalog" href="/identity/service-catalog" description="How tools find each service's endpoint in every region." />
</Cards>

## Guides

<Cards>
  <Card title="Get a token" href="/identity/get-a-token" description="Get a token with the CLI or the Identity API and send it with a request." />

  <Card title="Create an application credential" href="/identity/application-credentials" description="Give scripts and tools their own credential instead of your password." />
</Cards>

URL: https://docs.vexxhost.com/identity

---

# Authentication

> Every API request carries a token from Identity, which proves who you are and is valid for one project until it expires.

Every API request carries a token. A token is a string that Identity issues when you
prove who you are with a password or an [application credential](/identity/application-credentials).
Services check the token with Identity, so you never sign in to them separately.

## What a token is valid for

A token belongs to one user and is scoped to one [project](/identity/projects). It carries the
roles you have in that project, and services use them to decide what you may do. A token is valid
until it expires or is revoked, whichever comes first. The `expires` field that Identity returns
with it shows when.

A token is not a long-term credential. Do not store one in configuration. Store an application
credential and get a new token from it when you need one.

## How services receive it

Services read the token from the `X-Auth-Token` header. This request asks Identity itself which
projects the token's user can access:

```http
GET /v3/auth/projects HTTP/1.1
Host: auth.vexxhost.net
X-Auth-Token: gAAAAABnA4tJq8r2Lw0m5X1zY7cT3vH9kQ6pN4sB2dF8gR0jU5eW1iO7aZ3xC9yV6nM4bK2lE8hG0fD5tS1uP7qA3wI9oJ6zL4cX2vN8mB5kR1yT7eU3iO9pS6dF2gH0jK4lZ8xC5vB1nM3qW7eR9tY2uI6oP0aS4dF8gJ
```

Some services also accept the token as `Authorization: Bearer`. Each API reference lists the
headers its service accepts.

The `openstack` CLI and the OpenStack SDKs get tokens for you, renew them before they expire, and
add the header to every request.

## When a token is refused

| Response           | Meaning                                                                | What to do                                                  |
| ------------------ | ---------------------------------------------------------------------- | ----------------------------------------------------------- |
| `401 Unauthorized` | The token is missing, has expired, was revoked, or is not a token.     | Get a new token and send the request again.                 |
| `403 Forbidden`    | The token is valid, but its project or roles do not allow the request. | Check which [project](/identity/projects) the token is for. |

Anyone who holds a token can act as you in its project until the token expires. Treat a token like
a password: do not log it, paste it into tickets, or commit it.

## Related pages

* [Get a token](/identity/get-a-token)
* [Create an application credential](/identity/application-credentials)

URL: https://docs.vexxhost.com/identity/authentication

---

# Projects

> Every resource belongs to a project, and a token's project decides which resources you see and change.

A project is the container your resources belong to, such as servers, volumes, networks and
images. Every token is scoped to one project, and services show and change only that project's
resources.

For example, with a token for the project `a6f1c2d93b8e4f07b5d4e2c1a9f8e7d6`, listing servers
returns that project's servers only. Another project's resources are not visible: services refuse
requests for them, usually with `403 Forbidden` or `404 Not Found`.

## Your projects

A user can have roles in several projects. List them, and see which one your current token is for:

```sh
openstack project list --my-projects
openstack token issue -c project_id
```

## Choosing a project

The CLI and the OpenStack SDKs use the project set in your credentials. To work in another
project, choose it for one command, for a shell, or in a `clouds.yaml` entry:

```sh
openstack --os-project-id 7d3e9b1c5a2f4e8d9c6b0a1f3e5d7c9b server list
export OS_PROJECT_ID=7d3e9b1c5a2f4e8d9c6b0a1f3e5d7c9b
```

An [application credential](/identity/application-credentials) always acts on the project it was
created in. To automate work in several projects, create one credential per project.

URL: https://docs.vexxhost.com/identity/projects

---

# Service catalog

> Identity's service catalog lists every service's endpoints by region, so the OpenStack CLI and SDKs find services from the Identity URL alone.

The service catalog is the list of services that Identity returns with every token. For each
service it gives a type, such as `compute` or `network`, and the service's endpoints: one per
region and interface. Tools read the catalog to find a service, so the only URL you configure is
Identity's, `https://auth.vexxhost.net/v3`.

## Regions and interfaces

Most services run in each region separately, with an endpoint per region. A few services are
global: they have one endpoint that covers every region, and take the region as part of each
request instead. The catalog shows which is which.

Each endpoint also has an interface. Use `public`, which is the only interface reachable from the
internet. `internal` and `admin` endpoints are for internal use.

## Seeing your catalog

The catalog changes when new services or regions launch, so look it up rather than copying
endpoints into configuration. To see it:

```sh
openstack catalog list
openstack catalog show compute
openstack region list
```

Through the API, `GET https://auth.vexxhost.net/v3/auth/catalog` returns it for the token you send,
and the response to a token request includes it as `token.catalog`.

## Choosing a region

For a service that runs in each region, tools use the region you set:

* With the CLI, pass `--os-region-name <region>` or set `OS_REGION_NAME`.
* In `clouds.yaml`, set `region_name: <region>` in the cloud's entry.
* With the OpenStack SDK, pass `region_name` to `openstack.connect()`.

Set one region per `clouds.yaml` entry, and add an entry per region if you work in several.

## Overriding an endpoint

Tools should always find endpoints in the catalog. If you need to send one service's requests to
another URL, for example while testing, set an override for that service type in the cloud's
`clouds.yaml` entry. Write the service type with underscores:

```yaml title="~/.config/openstack/clouds.yaml"
clouds:
  vexxhost:
    block_storage_endpoint_override: https://block-storage.example.com/v3
```

In code, clients built on keystoneauth accept the same as `endpoint_override`.

URL: https://docs.vexxhost.com/identity/service-catalog

---

# Get a token

> Get an Identity token with the OpenStack CLI or the Identity API, and send it with an API request.

This guide gets a token from Identity and uses it to call an API. You only need to do this for
direct API calls: the `openstack` CLI and the OpenStack SDKs get tokens for you.

## Before you begin

You need either the `openstack` CLI [signed in](/developer/cli), or an
[application credential](/identity/application-credentials) for calls with `curl`.

## 1. Get the token

<Tabs items="['CLI', 'API']">
  <Tab value="CLI">
    The CLI prints a token for the project it is signed in to. Keep only the token in a variable:

    ```sh
    export TOKEN=$(openstack token issue -f value -c id)
    ```

    To see which project the token is for and when it expires, show those fields instead:

    ```sh
    openstack token issue -c expires -c project_id -c user_id
    ```

    ```
    +------------+----------------------------------+
    | Field      | Value                            |
    +------------+----------------------------------+
    | expires    | 2026-10-07T23:41:09+0000         |
    | project_id | a6f1c2d93b8e4f07b5d4e2c1a9f8e7d6 |
    | user_id    | 4c8e2f1a9b7d4e6f8a3c5b1d7e9f2a4c |
    +------------+----------------------------------+
    ```
  </Tab>

  <Tab value="API">
    Send an application credential to Identity. The token comes back in the `X-Subject-Token`
    response header:

    ```sh
    curl --silent --include https://auth.vexxhost.net/v3/auth/tokens \
      --header "Content-Type: application/json" \
      --data '{
        "auth": {
          "identity": {
            "methods": ["application_credential"],
            "application_credential": {
              "id": "3c2b8e1f4a6d4c0e9b7a5f3d1e2c4b6a",
              "secret": "Jq7mX2vL9pR4tW8yZ1bC5nF3hK6dS0gAuE2iO7qT4wY9rU1oP3aL6sD8fG5hJ2kZ0xC7vB4nM1qW8eR3tY6u"
            }
          }
        }
      }'
    ```

    ```http
    HTTP/1.1 201 Created
    Content-Type: application/json
    X-Subject-Token: gAAAAABnA4tJq8r2Lw0m5X1zY7cT3vH9kQ6pN4sB2dF8gR0jU5eW1iO7aZ3xC9yV6nM4bK2lE8hG0fD5tS1uP7qA3wI9oJ6zL4cX2vN8mB5kR1yT7eU3iO9pS6dF2gH0jK4lZ8xC5vB1nM3qW7eR9tY2uI6oP0aS4dF8gJ
    ```

    The JSON body describes the token: its `expires_at`, its `project`, and the
    [service catalog](/identity/service-catalog). Copy the `X-Subject-Token` value into `TOKEN`.
  </Tab>
</Tabs>

## 2. Send it with a request

Put the token in the `X-Auth-Token` header. This request asks Identity which projects you can
access, which works with any valid token:

```sh
curl https://auth.vexxhost.net/v3/auth/projects \
  --header "X-Auth-Token: $TOKEN"
```

```json
{
  "links": {
    "self": "https://auth.vexxhost.net/v3/auth/projects",
    "previous": null,
    "next": null
  },
  "projects": [
    {
      "id": "a6f1c2d93b8e4f07b5d4e2c1a9f8e7d6",
      "name": "acme-production",
      "description": "Production workloads",
      "domain_id": "default",
      "enabled": true,
      "is_domain": false,
      "parent_id": "default",
      "links": {
        "self": "https://auth.vexxhost.net/v3/projects/a6f1c2d93b8e4f07b5d4e2c1a9f8e7d6"
      }
    }
  ]
}
```

Other services take the token the same way. Find their endpoints in the
[service catalog](/identity/service-catalog).

## Verify

A request with a valid token gets a `200 OK` response. A `401 Unauthorized` response means the
token is missing or has expired. Get a new one.

## Next steps

* Learn how tokens work in [Authentication](/identity/authentication).
* [Make API requests](/developer/api) to other services with the token.

URL: https://docs.vexxhost.com/identity/get-a-token

---

# Create an application credential

> Create an Identity application credential so that scripts, CI jobs and tools can get tokens for one project without your password.

An application credential is an ID and secret that can get tokens for one project. Use one
wherever your password would otherwise be stored, such as in a script, a CI pipeline, or a
monitoring tool. You can limit its roles, give it an expiry date, and delete it at any time
without changing your password.

## Before you begin

You need the `openstack` CLI [signed in](/developer/cli) to the project the credential is for. A
credential always acts on the project it was created in.

## 1. Create the credential

```sh
openstack application credential create billing-reports \
  --description "Monthly cost export" \
  --expiration 2027-01-01T00:00:00 \
  -c id -c name -c expires_at -c secret
```

```
+------------+----------------------------------------------------------------------------------------+
| Field      | Value                                                                                  |
+------------+----------------------------------------------------------------------------------------+
| expires_at | 2027-01-01T00:00:00.000000                                                             |
| id         | 3c2b8e1f4a6d4c0e9b7a5f3d1e2c4b6a                                                       |
| name       | billing-reports                                                                        |
| secret     | Jq7mX2vL9pR4tW8yZ1bC5nF3hK6dS0gAuE2iO7qT4wY9rU1oP3aL6sD8fG5hJ2kZ0xC7vB4nM1qW8eR3tY6u |
+------------+----------------------------------------------------------------------------------------+
```

Identity shows the `secret` only once. Store it in your secret manager now.

These options narrow what the credential can do:

| Option                | Effect                                                                                          |
| --------------------- | ----------------------------------------------------------------------------------------------- |
| `--expiration <time>` | The credential stops working at this time.                                                      |
| `--role <role>`       | Give it only this role in the project. Repeat for more roles. Without it, it gets all of yours. |
| `--unrestricted`      | Allow it to create and delete other credentials. Leave this off unless a tool needs it.         |

## 2. Use the credential

Tools that read `clouds.yaml`, including the `openstack` CLI and the OpenStack SDK, take the
credential like this:

```yaml title="~/.config/openstack/clouds.yaml"
clouds:
  billing:
    auth_type: v3applicationcredential
    auth:
      auth_url: https://auth.vexxhost.net/v3
      application_credential_id: 3c2b8e1f4a6d4c0e9b7a5f3d1e2c4b6a
      application_credential_secret: Jq7mX2vL9pR4tW8yZ1bC5nF3hK6dS0gAuE2iO7qT4wY9rU1oP3aL6sD8fG5hJ2kZ0xC7vB4nM1qW8eR3tY6u
```

Tools that read environment variables take the same values as `OS_AUTH_TYPE`, `OS_AUTH_URL`,
`OS_APPLICATION_CREDENTIAL_ID` and `OS_APPLICATION_CREDENTIAL_SECRET`. The credential carries its
project, so it needs no project or domain settings.

## Verify

```sh
openstack --os-cloud billing token issue -c project_id
```

The output shows the project the credential was created in.

## Rotate or delete a credential

To rotate a credential, create a new one, switch your tool to it, then delete the old one:

```sh
openstack application credential delete billing-reports
```

Deleting a credential stops it from getting new tokens immediately. Tokens it already issued work
until they expire.

## Next steps

* [Get a token](/identity/get-a-token) from the credential for direct API calls.
* [Use OpenStack from Python](/developer/python) with the credential.

URL: https://docs.vexxhost.com/identity/application-credentials

---

# Cost Explorer

> See what your projects spend, by service, region and resource, and forecast future spending.

- Endpoint: https://cost-explorer.public.global.vexxhost.net
- OpenAPI schema: https://docs.vexxhost.com/cost-explorer/openapi.json (also https://docs.vexxhost.com/cost-explorer/openapi.yaml)
- Authentication: a token in the `X-Auth-Token` header; see https://docs.vexxhost.com/identity/authentication.md
- Requests, errors and rate limits: https://docs.vexxhost.com/developer/api.md
- CLI: `pip install vexxhost-costexplorerclient`, then `openstack ...` commands; setup in https://docs.vexxhost.com/developer/cli.md

- [Cost Explorer](https://docs.vexxhost.com/cost-explorer.md): See what your projects spend, by service, region and resource, and forecast future spending.

## Get started

- [Get started](https://docs.vexxhost.com/cost-explorer/get-started.md): Get your first Cost Explorer report with the OpenStack CLI, showing what a project spent last month.

## Concepts

- [Cost calculation](https://docs.vexxhost.com/cost-explorer/concepts/cost-calculation.md): Cost Explorer estimates costs from how long each server, volume, image and object storage bucket existed and the price of each, before taxes, credits and discounts.
- [Forecasts](https://docs.vexxhost.com/cost-explorer/concepts/forecasts.md): A Cost Explorer forecast projects your recent average daily spending over a future period of up to 90 days.

## Guides

- [Break down your costs](https://docs.vexxhost.com/cost-explorer/guides/break-down-costs.md): Break Cost Explorer reports down by service, region, usage type or resource, filter what they count, and add usage quantities.
- [Forecast your spending](https://docs.vexxhost.com/cost-explorer/guides/forecast-spending.md): Forecast a project's spending for the coming days or months with Cost Explorer, based on its recent daily average.
- [Use the Python client](https://docs.vexxhost.com/cost-explorer/guides/use-the-python-client.md): Install the Cost Explorer Python client, create it from an OpenStack SDK connection, and handle the errors it raises.

## API reference

- [Cost Explorer API](https://docs.vexxhost.com/cost-explorer/api.md): Call the Cost Explorer API: base URL, authentication, a first request, and every operation.
- [POST Calculate a cost forecast](https://docs.vexxhost.com/cost-explorer/api/cost-forecasts/create-cost-forecast.md)
- [POST Calculate a cost and usage report](https://docs.vexxhost.com/cost-explorer/api/cost-reports/create-cost-report.md)

## CLI reference

- [Cost Explorer CLI reference](https://docs.vexxhost.com/cost-explorer/cli.md): Look up every Cost Explorer command in the openstack CLI and its options.
- [openstack cost forecast create](https://docs.vexxhost.com/cost-explorer/cli/cost-forecast/cost-forecast-create.md): Forecast estimated spend using a trailing daily average.
- [openstack cost report create](https://docs.vexxhost.com/cost-explorer/cli/cost-report/cost-report-create.md): Compute an estimated cost and usage report (not a stored resource).

## Platform

- [Limits](https://docs.vexxhost.com/cost-explorer/platform/limits.md): The limits that apply to Cost Explorer reports and forecasts.

## Optional

- [All Cost Explorer pages in one file](https://docs.vexxhost.com/cost-explorer/llms-full.txt)

URL: https://docs.vexxhost.com/cost-explorer

---

# Get started

> Get your first Cost Explorer report with the OpenStack CLI, showing what a project spent last month.

This page takes you from nothing to a report of what your project spent last month. It takes
about two minutes.

## Before you begin

You need the `openstack` CLI, signed in to the project you want to report on.
[Set up the CLI](/developer/cli) if you have not already.

## 1. Install the Cost Explorer commands

```sh
pip install vexxhost-costexplorerclient
```

## 2. Report last month's total

Ask for one monthly total, from the first day of last month to the first day of this month:

```sh
openstack cost report create \
  --start 2026-09-01 \
  --end 2026-10-01 \
  --granularity monthly \
  --no-group
```

```
+----------------------+----------------------+-----------+--------+-------------------+------+
| Start                | End                  | Estimated | Metric | Amount            | Unit |
+----------------------+----------------------+-----------+--------+-------------------+------+
| 2026-09-01T00:00:00Z | 2026-10-01T00:00:00Z | True      | cost   | 1084.296000000000 | USD  |
+----------------------+----------------------+-----------+--------+-------------------+------+
```

The project spent an estimated 1084.30 USD in September. The period includes its start date and
excludes its end date, and dates are midnight UTC.

## 3. See where it went

Break the first three days of the month down by service, one row per service per day:

```sh
openstack cost report create \
  --start 2026-09-01 \
  --end 2026-09-04 \
  --granularity daily \
  --group-by service \
  --metric cost \
  --detail
```

```
+----------------------+----------------------+-----------+---------+--------+-----------------+------+
| Start                | End                  | Estimated | service | Metric | Amount          | Unit |
+----------------------+----------------------+-----------+---------+--------+-----------------+------+
| 2026-09-01T00:00:00Z | 2026-09-02T00:00:00Z | True      | compute | cost   | 31.104000000000 | USD  |
| 2026-09-01T00:00:00Z | 2026-09-02T00:00:00Z | True      | volume  | cost   | 4.808000000000  | USD  |
| 2026-09-02T00:00:00Z | 2026-09-03T00:00:00Z | True      | compute | cost   | 31.104000000000 | USD  |
| 2026-09-02T00:00:00Z | 2026-09-03T00:00:00Z | True      | volume  | cost   | 5.040000000000  | USD  |
| 2026-09-03T00:00:00Z | 2026-09-04T00:00:00Z | True      | compute | cost   | 31.104000000000 | USD  |
| 2026-09-03T00:00:00Z | 2026-09-04T00:00:00Z | True      | volume  | cost   | 5.040000000000  | USD  |
+----------------------+----------------------+-----------+---------+--------+-----------------+------+
```

Servers cost about 31.10 a day and volumes about 5, with volume costs rising on September 2.

## Next steps

* [Break down your costs](/cost-explorer/guides/break-down-costs) by region, usage type or resource, and filter
  what a report counts.
* Learn what is charged and how in [Cost calculation](/cost-explorer/concepts/cost-calculation).
* [Forecast your spending](/cost-explorer/guides/forecast-spending) for the rest of the month.

URL: https://docs.vexxhost.com/cost-explorer/get-started

---

# Cost calculation

> Cost Explorer estimates costs from how long each server, volume, image and object storage bucket existed and the price of each, before taxes, credits and discounts.

Cost Explorer estimates what your resources cost from two things: how long each one existed, and
its published price. Every amount it returns is an estimate, marked `"estimated": true`, including
those for months that have ended.

An estimate is not an invoice. It does not include taxes, credits, or discounts on your account,
and amounts are not rounded to cents. Use Cost Explorer to see where spending goes and to track
it over time. Your invoice remains the final amount.

## What is charged

| Service   | Usage type                                   | Charged for                                                              | Unit        |
| --------- | -------------------------------------------- | ------------------------------------------------------------------------ | ----------- |
| `compute` | The server's flavor, such as `v3-standard-2` | Every hour from the server's creation until its deletion                 | `Hours`     |
| `volume`  | The volume type, such as `ssd` or `nvme`     | The volume's size for every hour it exists                               | `GiB-Hours` |
| `image`   | `stored`                                     | The image's size in whole GiB, rounded down, for every hour it is stored | `GiB-Hours` |
| `object`  | The storage class, such as `standard`        | The bytes each bucket or container holds, for every hour it holds them   | `GiB-Hours` |

A server costs the same whether it is running or stopped, because a stopped server still holds its
resources. Only deleting it ends its charges. The same is true of volumes and images.

### Object storage

Object storage is measured the way S3 measures it. About once an hour, Cost Explorer reads how many
bytes each bucket holds, counting every object version and the parts of uploads that have not
finished. That size is charged until the next reading that finds a different one. A bucket's
`resource_id` is its name, prefixed with your project ID: `<project_id>/<bucket>`. Swift containers
are buckets too, so they appear the same way.

Because the size is a reading rather than a record, usage starts when Cost Explorer first reads a
bucket. A bucket that already existed before then is not charged for the time before it.

Object storage time is not rounded up to the minute: a bucket is charged for exactly the time it
holds its bytes.

## Prices and rounding

Hourly prices apply to each hour of use. Monthly prices, such as most storage, are spread evenly
over the hours of each calendar month, so the hourly rate is slightly lower in a 31-day month than
in a 30-day one.

Cost Explorer counts time in whole minutes, rounded up, and counts each calendar month
separately. A
server that runs for 90 seconds is charged for two minutes.

### Tiered prices

Some prices fall as you use more, such as object storage: the first TiB-month of the month costs
one rate, the usage beyond it a lower one, and so on. Each tier's price applies only to the usage
inside that tier, so using more never makes the earlier usage cheaper. Tiers count one project's
usage in one region, from the start of each calendar month.

Usage moves through the tiers in the order it happens, so the first days of a month are charged at
the first tier's rate and later days at lower rates once the project passes a threshold. A report
that starts partway through a month continues from the tier the month had already reached, so a
month's daily amounts add up to its monthly amount. When several buckets share a tier, each is
charged the tier's average rate for that period, in proportion to its size.

Amounts are decimal strings with up to 12 decimal places, so daily amounts add up exactly to the
monthly total. Each report names the `currency` and the `pricing_version` it used.

## How current the data is

Recent usage takes a little time to arrive. Every report lists each region it covers with
`data_through`, the time its usage is complete up to:

```json
"regions": [{"name": "ca-ymq-1", "data_through": "2026-10-07T14:00:00Z"}]
```

Usage after that time is not counted yet, so the most recent hours of a report can grow when you ask
again later.

## Who can see what

A report covers the [project](/identity/projects) your token is scoped to.
Asking for another project returns `403 Forbidden`.

URL: https://docs.vexxhost.com/cost-explorer/concepts/cost-calculation

---

# Forecasts

> A Cost Explorer forecast projects your recent average daily spending over a future period of up to 90 days.

A forecast estimates what a project is going to spend over a future period if it keeps spending at its
recent rate. Cost Explorer averages the daily cost over a lookback period, 7 days unless you choose
otherwise, and projects that average forward.

For example, if a project spent 245 over the last 7 days, its average is 35 a day, and the forecast
for the next 30 days is 1050.

A forecast is a straight-line estimate, so:

* It does not know about resources you are about to create or delete.
* It does not follow trends: a project whose spending is growing is under-forecast.
* It does not account for price changes or seasonal patterns.
* It forecasts zero for a project with no spending in the lookback period.

## Choosing a lookback

A longer lookback smooths out one-off spikes, such as a large test that ran for a day. A shorter
one reacts faster when you add or remove resources. The lookback can be from 1 to 90 days.

## Forecasts and budgets

To be alerted when a month is on course to cost more than you expect, use a
[budget with a forecasted threshold](/budgets/concepts/thresholds) instead
of checking forecasts yourself. Budgets projects the spending so far in the current window to its
end.

URL: https://docs.vexxhost.com/cost-explorer/concepts/forecasts

---

# Break down your costs

> Break Cost Explorer reports down by service, region, usage type or resource, filter what they count, and add usage quantities.

This guide reports a month's costs per service and per day, narrows a report to one service or
region, and adds how much of each resource type you used.

## Before you begin

Sign in with the [CLI](/developer/cli), get a
[token](/identity/get-a-token) for the API, or create the
[Python client](/cost-explorer/guides/use-the-python-client).

## 1. Choose the period and granularity

A report covers a period from `start` up to, but not including, `end`, and splits it into
`hourly`, `daily` or `monthly` rows. A period can be up to 366 days long. Prefer `daily` or
`monthly` for long periods, because hourly rows make large reports.

## 2. Group by what you want to compare

`group_by` splits each row into groups. Group by any of `service`, `region`, `usage_type`,
`project_id` and `resource_id`. Grouping by `resource_id` shows what each server, volume and
image cost.

This request reports September's costs per service, per day:

<CodeBlockTabs defaultValue="CLI">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="CLI">
      CLI
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="API">
      API
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="Python">
      Python
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="CLI">
    ```sh
    openstack cost report create \
      --start 2026-09-01 \
      --end 2026-10-01 \
      --granularity daily \
      --group-by service \
      --metric cost \
      --detail
    ```
  </CodeBlockTab>

  <CodeBlockTab value="API">
    ```sh
    curl --request POST https://cost-explorer.public.global.vexxhost.net/v1/cost_reports \
      --header "X-Auth-Token: $TOKEN" \
      --header "Content-Type: application/json" \
      --data '{
        "time_period": {"start": "2026-09-01T00:00:00Z", "end": "2026-10-01T00:00:00Z"},
        "granularity": "daily",
        "metrics": ["cost"],
        "group_by": ["service"]
      }'
    ```
  </CodeBlockTab>

  <CodeBlockTab value="Python">
    ```python
    report = costs.cost_reports.create(
        time_period={"start": "2026-09-01T00:00:00Z", "end": "2026-10-01T00:00:00Z"},
        granularity="daily",
        metrics=["cost"],
        group_by=["service"],
    )
    ```
  </CodeBlockTab>
</CodeBlockTabs>

`--detail` adds the per-group rows to the CLI's table. The API returns one entry per day, each
with a total and a group per service:

```json
{
  "as_of": "2026-10-07T15:12:40Z",
  "currency": "USD",
  "pricing_version": "legacy-usd-21c6b51",
  "group_definitions": ["service"],
  "regions": [{"name": "ca-ymq-1", "data_through": "2026-10-07T14:00:00Z"}],
  "results_by_time": [
    {
      "time_period": {"start": "2026-09-01T00:00:00Z", "end": "2026-09-02T00:00:00Z"},
      "estimated": true,
      "total": {"cost": {"amount": "35.912000000000", "unit": "USD"}},
      "groups": [
        {"keys": ["compute"], "metrics": {"cost": {"amount": "31.104000000000", "unit": "USD"}}},
        {"keys": ["volume"], "metrics": {"cost": {"amount": "4.808000000000", "unit": "USD"}}}
      ]
    }
  ]
}
```

The `keys` of each group follow the order of `group_definitions`. The response continues with an
entry for every day of September.

## 3. Filter what counts

Filters narrow a report to some of your spending. A filter with several values matches any of
them, and different filters must all match.

| Filter                                            | CLI                          | API                                            |
| ------------------------------------------------- | ---------------------------- | ---------------------------------------------- |
| Service: `compute`, `volume`, `image` or `object` | `--service compute`          | `"filter": {"services": ["compute"]}`          |
| Region                                            | `--region ca-ymq-1`          | `"filter": {"regions": ["ca-ymq-1"]}`          |
| Usage type, such as a flavor                      | `--usage-type v3-standard-2` | `"filter": {"usage_types": ["v3-standard-2"]}` |

On the CLI, repeat an option to give several values. In the API, leave a filter out to count
everything. An empty list matches nothing.

## 4. Add usage quantities

To see how much you used as well as what it cost, add the `usage_quantity` metric. Hours and
GiB-hours cannot be added together, so this metric needs grouping by `usage_type`:

<CodeBlockTabs defaultValue="CLI">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="CLI">
      CLI
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="API">
      API
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="CLI">
    ```sh
    openstack cost report create \
      --start 2026-09-01 \
      --end 2026-10-01 \
      --granularity monthly \
      --group-by usage_type \
      --metric cost \
      --metric usage_quantity \
      --detail
    ```
  </CodeBlockTab>

  <CodeBlockTab value="API">
    ```json
    {
      "time_period": {"start": "2026-09-01T00:00:00Z", "end": "2026-10-01T00:00:00Z"},
      "granularity": "monthly",
      "metrics": ["cost", "usage_quantity"],
      "group_by": ["usage_type"]
    }
    ```
  </CodeBlockTab>
</CodeBlockTabs>

Each group then has a `usage_quantity` in `Hours` or `GiB-Hours`, and each row has
`usage_by_unit` totals.

## Verify

Add up the daily totals of a month and compare them with a monthly report for the same month. They
are equal, because Cost Explorer counts each minute in exactly one row. If the most recent rows look
low, check `data_through`: usage after that time is not counted yet.

## Next steps

* [Forecast your spending](/cost-explorer/guides/forecast-spending) for the rest of the month.
* Get alerted before you overspend with [Budgets](/budgets).
* Look up every field in the [API reference](/cost-explorer/api).

URL: https://docs.vexxhost.com/cost-explorer/guides/break-down-costs

---

# Forecast your spending

> Forecast a project's spending for the coming days or months with Cost Explorer, based on its recent daily average.

This guide forecasts a project's spending for the rest of the month. Read
[Forecasts](/cost-explorer/concepts/forecasts) first to know what a forecast can and cannot predict.

## Before you begin

Sign in with the [CLI](/developer/cli), get a
[token](/identity/get-a-token) for the API, or create the
[Python client](/cost-explorer/guides/use-the-python-client).

## 1. Request the forecast

A forecast period starts today or later and ends within 90 days. Choose `daily` rows to see each
day, or `monthly` for one total per month. This request forecasts from today, October 7, to the
end of October, using the last 7 days of spending:

<CodeBlockTabs defaultValue="CLI">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="CLI">
      CLI
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="API">
      API
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="Python">
      Python
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="CLI">
    ```sh
    openstack cost forecast create \
      --start 2026-10-07 \
      --end 2026-11-01 \
      --granularity monthly \
      --lookback-days 7
    ```
  </CodeBlockTab>

  <CodeBlockTab value="API">
    ```sh
    curl --request POST https://cost-explorer.public.global.vexxhost.net/v1/cost_forecasts \
      --header "X-Auth-Token: $TOKEN" \
      --header "Content-Type: application/json" \
      --data '{
        "time_period": {"start": "2026-10-07T00:00:00Z", "end": "2026-11-01T00:00:00Z"},
        "granularity": "monthly",
        "lookback_days": 7
      }'
    ```
  </CodeBlockTab>

  <CodeBlockTab value="Python">
    ```python
    forecast = costs.cost_forecasts.create(
        time_period={"start": "2026-10-07T00:00:00Z", "end": "2026-11-01T00:00:00Z"},
        granularity="monthly",
        lookback_days=7,
    )
    ```
  </CodeBlockTab>
</CodeBlockTabs>

Forecasts take the same filters as cost reports. See
[Break down your costs](/cost-explorer/guides/break-down-costs#3-filter-what-counts).

## 2. Read the result

```
+----------------------+----------------------+------------------+------+-----------+
| Start                | End                  | Amount           | Unit | Estimated |
+----------------------+----------------------+------------------+------+-----------+
| 2026-10-07T15:20:00Z | 2026-11-01T00:00:00Z | 865.425000000000 | USD  | True      |
+----------------------+----------------------+------------------+------+-----------+
```

The API returns the same forecast with the history it averaged:

```json
{
  "as_of": "2026-10-07T15:20:00Z",
  "model": "trailing_spend_average",
  "estimated": true,
  "history_period": {"start": "2026-09-30T15:20:00Z", "end": "2026-10-07T15:20:00Z"},
  "total": {"amount": "865.425000000000", "unit": "USD"},
  "regions": [{"name": "ca-ymq-1", "data_through": "2026-10-07T14:00:00Z"}],
  "forecast_results_by_time": [
    {
      "time_period": {"start": "2026-10-07T15:20:00Z", "end": "2026-11-01T00:00:00Z"},
      "mean_value": "865.425000000000"
    }
  ]
}
```

A period that starts today begins at the current time, which `as_of` shows. The project is
forecast to spend about 865 more by the end of October.

## Verify

Compare the forecast with what the month has cost so far: add a cost report from October 1 to
today, and the sum is the expected total for October.

## Next steps

* Get alerted when a month is forecast to go over with a
  [budget's forecasted threshold](/budgets/concepts/thresholds).
* Look up every field in the [API reference](/cost-explorer/api).

URL: https://docs.vexxhost.com/cost-explorer/guides/forecast-spending

---

# Use the Python client

> Install the Cost Explorer Python client, create it from an OpenStack SDK connection, and handle the errors it raises.

The `vexxhost-costexplorerclient` package is the Python client for Cost Explorer. It signs in with the same session as the
OpenStack SDK, so it works with your existing `clouds.yaml`.

## Before you begin

Set up a `clouds.yaml` entry or an application credential. See
[Use OpenStack from Python](/developer/python).

## 1. Install the client

```sh
pip install vexxhost-costexplorerclient
```

## 2. Create the client

```python
import openstack
from costexplorerclient import Client

conn = openstack.connect(cloud="vexxhost")
costs = Client("1", session=conn.session)
```

The client finds Cost Explorer in the [service catalog](/identity/service-catalog).
The other Cost Explorer guides call it `costs`.

## 3. Make a call

Methods take and return dictionaries with the same fields as the API's JSON, so the
[API reference](/cost-explorer/api) also describes each call. `costs.cost_reports` creates reports and `costs.cost_forecasts` creates forecasts.

```python
report = costs.cost_reports.create(
    time_period={"start": "2026-09-01T00:00:00Z", "end": "2026-10-01T00:00:00Z"},
    granularity="monthly",
)
print(report["results_by_time"][0]["total"]["cost"]["amount"])
```

## 4. Handle errors

A failed request raises an exception from `costexplorerclient.exceptions`, such as `Conflict`,
`TooManyRequests` or `Unauthorized`. The exceptions are also `keystoneauth1.exceptions` HTTP errors.

| Attribute     | Contents                                                                 |
| ------------- | ------------------------------------------------------------------------ |
| `status_code` | The HTTP status.                                                         |
| `problem`     | The API's error document, with `title`, `detail` and any field `errors`. |
| `request_id`  | The `X-Request-ID` of the request, for support.                          |
| `retry_after` | Seconds to wait before retrying, on `429 Too Many Requests`.             |

```python
from costexplorerclient import exceptions

try:
    costs.cost_reports.create(time_period={"start": "2026-09-01T00:00:00Z", "end": "2026-10-01T00:00:00Z"}, granularity="monthly")
except exceptions.ClientException as error:
    print(error.status_code, error.request_id, error.problem["detail"])
    raise
```

## Next steps

* Use `costs` in the Python examples of the other Cost Explorer guides.

URL: https://docs.vexxhost.com/cost-explorer/guides/use-the-python-client

---

# Cost Explorer API

> Call the Cost Explorer API: base URL, authentication, a first request, and every operation.

The Cost Explorer API is a JSON API over HTTPS. Estimated costs and usage of your projects, with forecasts.

## Base URL

```
https://cost-explorer.public.global.vexxhost.net
```

## Authentication

Send a token from [Identity](/identity/authentication) in the `Authorization: Bearer` or `X-Auth-Token` header with every request. See [Make API requests](/developer/api) for status codes and retries.

## Make your first request

Report what your project spent in September:

```sh
curl --request POST https://cost-explorer.public.global.vexxhost.net/v1/cost_reports \
  --header "X-Auth-Token: $TOKEN" \
  --header "Content-Type: application/json" \
  --data '{
    "time_period": {"start": "2026-09-01T00:00:00Z", "end": "2026-10-01T00:00:00Z"},
    "granularity": "monthly",
    "metrics": ["cost"],
    "group_by": []
  }'
```

```json
{
  "as_of": "2026-10-07T15:12:40Z",
  "currency": "USD",
  "pricing_version": "legacy-usd-21c6b51",
  "group_definitions": [],
  "regions": [{"name": "ca-ymq-1", "data_through": "2026-10-07T14:00:00Z"}],
  "results_by_time": [
    {
      "time_period": {"start": "2026-09-01T00:00:00Z", "end": "2026-10-01T00:00:00Z"},
      "estimated": true,
      "total": {"cost": {"amount": "1084.296000000000", "unit": "USD"}},
      "groups": []
    }
  ]
}
```

Reports and forecasts are computed for each request and are not stored, so both operations are
`POST` requests that return their result directly.

## Errors

Errors use the [problem details](https://www.rfc-editor.org/rfc/rfc9457) format, `application/problem+json`. `detail` says what went wrong, and a validation error lists each problem in `errors` with the `location` of the field:

```json
{
  "status": 422,
  "title": "Unprocessable Entity",
  "detail": "validation failed",
  "errors": [
    {
      "location": "body",
      "message": "expected required property time_period to be present",
      "value": {}
    }
  ]
}
```

Every response carries an `X-Request-ID` header. Include it when you contact support about a request.

When the service is busy it answers `429 Too Many Requests` with a `Retry-After` header. Wait that many seconds, then send the request again.

## Operations

| Method | Path                                                                           | Description                       |
| ------ | ------------------------------------------------------------------------------ | --------------------------------- |
| `POST` | [`/v1/cost_forecasts`](/cost-explorer/api/cost-forecasts/create-cost-forecast) | Calculate a cost forecast         |
| `POST` | [`/v1/cost_reports`](/cost-explorer/api/cost-reports/create-cost-report)       | Calculate a cost and usage report |

## OpenAPI schema

Download the schema as [JSON](/cost-explorer/openapi.json) or [YAML](/cost-explorer/openapi.yaml) to generate a client or import the API into a tool such as Postman.

URL: https://docs.vexxhost.com/cost-explorer/api

---

# Calculate a cost forecast

`POST https://cost-explorer.public.global.vexxhost.net/v1/cost_forecasts`

Projects the trailing daily average over a future period, up to 90 days. The result is an estimate.

Authentication: `X-Auth-Token: <token>` header or `Authorization: Bearer <token>` header. See https://docs.vexxhost.com/identity/authentication.md.

## Request body (application/json)

- `allow_partial` (boolean): When a region does not answer, forecast from the regions that did and list the rest in missing_regions, instead of failing the forecast.
- `filter` (object)
  - `project_ids` (array of string, nullable)
  - `regions` (array of string, nullable)
  - `services` (array of string, one of `compute`, `volume`, `image`, `object`, nullable)
  - `usage_types` (array of string, nullable)
- `granularity` (string, one of `daily`, `monthly`, required)
- `lookback_days` (integer, int64)
- `metric` (string, one of `cost`)
- `time_period` (object, required)
  - `end` (string, date-time, required): Exclusive interval end (RFC3339).
  - `start` (string, date-time, required): Inclusive UTC interval start (RFC3339).

## Responses

### 200 — OK

`application/json`:

- `as_of` (string, date-time, required)
- `estimated` (boolean, required)
- `forecast_results_by_time` (array of object, nullable, required)
  - `mean_value` (string, required)
  - `time_period` (object, required)
    - `end` (string, date-time, required): Exclusive interval end (RFC3339).
    - `start` (string, date-time, required): Inclusive UTC interval start (RFC3339).
- `history_period` (object, required)
  - `end` (string, date-time, required): Exclusive interval end (RFC3339).
  - `start` (string, date-time, required): Inclusive UTC interval start (RFC3339).
- `missing_regions` (array of string, nullable): Regions left out of an allow_partial forecast because they did not answer.
- `model` (string, required)
- `regions` (array of object, nullable): The regions the history covers and how far each one's data is complete.
  - `data_through` (string, date-time, required)
  - `name` (string, required)
- `total` (object, required)
  - `amount` (string, required)
  - `unit` (string, required)

### 400 — Bad Request

`application/problem+json`:

- `detail` (string): A human-readable explanation specific to this occurrence of the problem. Example: `Property foo is required but is missing.`.
- `errors` (array of object, nullable): Optional list of individual error details
  - `location` (string): Where the error occurred, e.g. 'body.items[3].tags' or 'path.thing-id'
  - `message` (string): Error message text
  - `value` (object): The value at the given location
- `instance` (string, uri): A URI reference that identifies the specific occurrence of the problem. Example: `https://example.com/error-log/abc123`.
- `status` (integer, int64): HTTP status code Example: `400`.
- `title` (string): A short, human-readable summary of the problem type. This value should not change between occurrences of the error. Example: `Bad Request`.
- `type` (string, uri): A URI reference to human-readable documentation for the error. Example: `https://example.com/errors/example`.

### 401 — Unauthorized

Same `application/problem+json` body as 400.

### 403 — Forbidden

Same `application/problem+json` body as 400.

### 413 — Request Entity Too Large

Same `application/problem+json` body as 400.

### 422 — Unprocessable Entity

Same `application/problem+json` body as 400.

### 429 — Too Many Requests

Same `application/problem+json` body as 400.

### 500 — Internal Server Error

Same `application/problem+json` body as 400.

### 503 — Service Unavailable

Same `application/problem+json` body as 400.

### 504 — Gateway Timeout

Same `application/problem+json` body as 400.

Full schema: https://docs.vexxhost.com/cost-explorer/openapi.json

URL: https://docs.vexxhost.com/cost-explorer/api/cost-forecasts/create-cost-forecast

---

# Calculate a cost and usage report

`POST https://cost-explorer.public.global.vexxhost.net/v1/cost_reports`

Computes costs and usage for a period that includes its start and excludes its end. Reports are not stored. A token reports on its own project.

Authentication: `X-Auth-Token: <token>` header or `Authorization: Bearer <token>` header. See https://docs.vexxhost.com/identity/authentication.md.

## Request body (application/json)

- `allow_partial` (boolean): When a region does not answer, return the regions that did and list the rest in missing_regions, instead of failing the report.
- `as_of` (string, date-time): Optional reporting cutoff; defaults to the current time and cannot be in the future.
- `filter` (object)
  - `project_ids` (array of string, nullable)
  - `regions` (array of string, nullable)
  - `services` (array of string, one of `compute`, `volume`, `image`, `object`, nullable)
  - `usage_types` (array of string, nullable)
- `granularity` (string, one of `hourly`, `daily`, `monthly`, required)
- `group_by` (array of string, one of `project_id`, `region`, `service`, `usage_type`, `resource_id`, nullable)
- `metrics` (array of string, one of `cost`, `usage_quantity`, nullable)
- `time_period` (object, required)
  - `end` (string, date-time, required): Exclusive interval end (RFC3339).
  - `start` (string, date-time, required): Inclusive UTC interval start (RFC3339).

## Responses

### 200 — OK

`application/json`:

- `as_of` (string, date-time, required)
- `currency` (string, required)
- `group_definitions` (array of string, nullable, required)
- `missing_regions` (array of string, nullable): Regions left out of an allow_partial report because they did not answer.
- `pricing_version` (string, required)
- `regions` (array of object, nullable): The regions this report covers and how far each one's data is complete.
  - `data_through` (string, date-time, required)
  - `name` (string, required)
- `results_by_time` (array of object, nullable, required)
  - `estimated` (boolean, required)
  - `groups` (array of object, nullable, required)
    - `keys` (array of string, nullable, required)
    - `metrics` (object, required)
  - `time_period` (object, required)
    - `end` (string, date-time, required): Exclusive interval end (RFC3339).
    - `start` (string, date-time, required): Inclusive UTC interval start (RFC3339).
  - `total` (object, required)
  - `usage_by_unit` (object)

### 400 — Bad Request

`application/problem+json`:

- `detail` (string): A human-readable explanation specific to this occurrence of the problem. Example: `Property foo is required but is missing.`.
- `errors` (array of object, nullable): Optional list of individual error details
  - `location` (string): Where the error occurred, e.g. 'body.items[3].tags' or 'path.thing-id'
  - `message` (string): Error message text
  - `value` (object): The value at the given location
- `instance` (string, uri): A URI reference that identifies the specific occurrence of the problem. Example: `https://example.com/error-log/abc123`.
- `status` (integer, int64): HTTP status code Example: `400`.
- `title` (string): A short, human-readable summary of the problem type. This value should not change between occurrences of the error. Example: `Bad Request`.
- `type` (string, uri): A URI reference to human-readable documentation for the error. Example: `https://example.com/errors/example`.

### 401 — Unauthorized

Same `application/problem+json` body as 400.

### 403 — Forbidden

Same `application/problem+json` body as 400.

### 413 — Request Entity Too Large

Same `application/problem+json` body as 400.

### 422 — Unprocessable Entity

Same `application/problem+json` body as 400.

### 429 — Too Many Requests

Same `application/problem+json` body as 400.

### 500 — Internal Server Error

Same `application/problem+json` body as 400.

### 503 — Service Unavailable

Same `application/problem+json` body as 400.

### 504 — Gateway Timeout

Same `application/problem+json` body as 400.

Full schema: https://docs.vexxhost.com/cost-explorer/openapi.json

URL: https://docs.vexxhost.com/cost-explorer/api/cost-reports/create-cost-report

---

# Cost Explorer CLI reference

> Look up every Cost Explorer command in the openstack CLI and its options.

The `vexxhost-costexplorerclient` package adds the Cost Explorer commands to the `openstack` CLI. This reference covers version 0.3.1. Install the package with pip:

```sh
pip install vexxhost-costexplorerclient
```

To sign in and choose an output format, see [Set up the CLI](/developer/cli).

| Command                                                                                   | Description                                                         |
| ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| [`openstack cost forecast create`](/cost-explorer/cli/cost-forecast/cost-forecast-create) | Forecast estimated spend using a trailing daily average.            |
| [`openstack cost report create`](/cost-explorer/cli/cost-report/cost-report-create)       | Compute an estimated cost and usage report (not a stored resource). |

URL: https://docs.vexxhost.com/cost-explorer/cli

---

# openstack cost forecast create

> Forecast estimated spend using a trailing daily average.

## Usage

```sh
openstack cost forecast create \
    --start START \
    --end END \
    [--granularity {daily,monthly}] \
    [--project PROJECT_ID] ... \
    [--region REGIONS] ... \
    [--service {compute,volume,image}] ... \
    [--usage-type USAGE_TYPES] ... \
    [--lookback-days LOOKBACK_DAYS]
```

## Arguments

* `--start START` *(required)*

  Inclusive start date/time; date-only values use UTC.

* `--end END` *(required)*

  Exclusive end date/time.

* `--granularity <granularity>`

  One of: `daily`, `monthly`.

  Default: `daily`.

* `--project PROJECT_ID` *(repeatable)*

  Filter project ID (repeatable); subject to token scope.

* `--region REGIONS` *(repeatable)*

  Filter usage region (repeatable); independent of --os-region-name.

* `--service <service>` *(repeatable)*

  One of: `compute`, `volume`, `image`.

* `--usage-type USAGE_TYPES` *(repeatable)*

  Filter usage type/flavor (repeatable).

* `--lookback-days LOOKBACK_DAYS`

  Default: `7`.

## Output

This command also takes the common output options, such as `-f json` and `-c <column>`. See [Set up the CLI](/developer/cli).

URL: https://docs.vexxhost.com/cost-explorer/cli/cost-forecast/cost-forecast-create

---

# openstack cost report create

> Compute an estimated cost and usage report (not a stored resource).

## Usage

```sh
openstack cost report create \
    --start START \
    --end END \
    [--granularity {hourly,daily,monthly}] \
    [--project PROJECT_ID] ... \
    [--region REGIONS] ... \
    [--service {compute,volume,image}] ... \
    [--usage-type USAGE_TYPES] ... \
    [--as-of AS_OF] \
    [--metric {cost,usage_quantity}] ... \
    [--group-by {project_id,region,service,usage_type,resource_id}] ... \
    [--no-group] \
    [--detail]
```

## Arguments

* `--start START` *(required)*

  Inclusive start date/time; date-only values use UTC.

* `--end END` *(required)*

  Exclusive end date/time.

* `--granularity <granularity>`

  One of: `hourly`, `daily`, `monthly`.

  Default: `daily`.

* `--project PROJECT_ID` *(repeatable)*

  Filter project ID (repeatable); subject to token scope.

* `--region REGIONS` *(repeatable)*

  Filter usage region (repeatable); independent of --os-region-name.

* `--service <service>` *(repeatable)*

  One of: `compute`, `volume`, `image`.

* `--usage-type USAGE_TYPES` *(repeatable)*

  Filter usage type/flavor (repeatable).

* `--as-of AS_OF`

  Reporting cutoff timestamp.

* `--metric <metric>` *(repeatable)*

  One of: `cost`, `usage_quantity`.

* `--group-by <group-by>` *(repeatable)*

  One of: `project_id`, `region`, `service`, `usage_type`, `resource_id`.

* `--no-group`

  Request cost totals only (defaults metric to cost).

* `--detail`

  Show each group/metric instead of period totals.

## Output

This command also takes the common output options, such as `-f json` and `-c <column>`. See [Set up the CLI](/developer/cli).

URL: https://docs.vexxhost.com/cost-explorer/cli/cost-report/cost-report-create

---

# Limits

> The limits that apply to Cost Explorer reports and forecasts.

## Reports

| Limit                       | Value          |
| --------------------------- | -------------- |
| Length of a report's period | Up to 366 days |
| Groups in one report        | Up to 100,000  |
| Request body size           | Up to 1 MiB    |

Cost Explorer refuses a report with too many groups with `400 Bad Request`. Narrow it with a filter, a
shorter period or a coarser granularity.

## Forecasts

| Limit           | Value                                         |
| --------------- | --------------------------------------------- |
| Forecast period | Starts today or later and ends within 90 days |
| Lookback        | 1 to 90 days, 7 by default                    |
| Granularity     | `daily` or `monthly`                          |

## Concurrent requests

When Cost Explorer is busy, a request waits up to five seconds for its turn. If it is still busy,
it answers `429 Too Many Requests` with `Retry-After: 5`. Wait that long and send the request
again. See [Make API requests](/developer/api#retry-safely).

URL: https://docs.vexxhost.com/cost-explorer/platform/limits

---

# Budgets

> Set spending limits for a project and get alerted by email or webhook before you cross them.

- Endpoint: https://budget.public.global.vexxhost.net
- OpenAPI schema: https://docs.vexxhost.com/budgets/openapi.json (also https://docs.vexxhost.com/budgets/openapi.yaml)
- Authentication: a token in the `X-Auth-Token` header; see https://docs.vexxhost.com/identity/authentication.md
- Requests, errors and rate limits: https://docs.vexxhost.com/developer/api.md
- CLI: `pip install vexxhost-budgetclient`, then `openstack ...` commands; setup in https://docs.vexxhost.com/developer/cli.md

- [Budgets](https://docs.vexxhost.com/budgets.md): Set spending limits for a project and get alerted by email or webhook before you cross them.

## Get started

- [Get started](https://docs.vexxhost.com/budgets/get-started.md): Create your first budget with the OpenStack CLI, so that Budgets emails you when a project has spent 80% of its monthly amount.

## Concepts

- [Budgets](https://docs.vexxhost.com/budgets/concepts/budgets.md): A budget is an amount a project may spend in each window of a period, with thresholds that decide when Budgets sends alerts.
- [Thresholds](https://docs.vexxhost.com/budgets/concepts/thresholds.md): Thresholds decide when a budget sends alerts, based on actual spending so far or on the forecast for the whole window.
- [Notification channels](https://docs.vexxhost.com/budgets/concepts/notification-channels.md): Notification channels are the email addresses and webhooks a project's budget alerts are sent to.

## Guides

- [Create a budget](https://docs.vexxhost.com/budgets/guides/create-a-budget.md): Create a Budgets budget with the CLI, the API or Python, choosing its period, the spending it counts and the thresholds that send alerts.
- [Receive alerts with a webhook](https://docs.vexxhost.com/budgets/guides/receive-webhook-alerts.md): Send Budgets alerts to your own HTTPS endpoint as signed JSON, verify each request, and handle retries.
- [Change a budget](https://docs.vexxhost.com/budgets/guides/change-a-budget.md): Change a Budgets budget's amount, filter or thresholds, pause it, or delete it, without overwriting someone else's change.
- [Use the Python client](https://docs.vexxhost.com/budgets/guides/use-the-python-client.md): Install the Budgets Python client, create it from an OpenStack SDK connection, and handle the errors it raises.

## API reference

- [Budgets API](https://docs.vexxhost.com/budgets/api.md): Call the Budgets API: base URL, authentication, a first request, and every operation.
- [GET List budgets](https://docs.vexxhost.com/budgets/api/budgets/list-budgets.md)
- [POST Create a budget](https://docs.vexxhost.com/budgets/api/budgets/create-budget.md)
- [GET Get a budget and the status of its current window](https://docs.vexxhost.com/budgets/api/budgets/get-budget.md)
- [PUT Replace a budget](https://docs.vexxhost.com/budgets/api/budgets/update-budget.md)
- [DELETE Delete a budget and its history](https://docs.vexxhost.com/budgets/api/budgets/delete-budget.md)
- [GET List a budget's alerts and their delivery, newest first](https://docs.vexxhost.com/budgets/api/budgets/list-budget-notifications.md)
- [GET List a budget's past and current windows, newest first](https://docs.vexxhost.com/budgets/api/budgets/list-budget-windows.md)
- [GET List notification channels](https://docs.vexxhost.com/budgets/api/notification-channels/list-notification-channels.md)
- [POST Create a notification channel](https://docs.vexxhost.com/budgets/api/notification-channels/create-notification-channel.md)
- [GET Get a notification channel](https://docs.vexxhost.com/budgets/api/notification-channels/get-notification-channel.md)
- [DELETE Delete a notification channel](https://docs.vexxhost.com/budgets/api/notification-channels/delete-notification-channel.md)
- [POST Send an email channel's confirmation link again](https://docs.vexxhost.com/budgets/api/notification-channels/resend-channel-confirmation.md)
- [POST Replace a webhook channel's signing secret](https://docs.vexxhost.com/budgets/api/notification-channels/rotate-channel-secret.md)

## CLI reference

- [Budgets CLI reference](https://docs.vexxhost.com/budgets/cli.md): Look up every Budgets command in the openstack CLI and its options.
- [openstack budget channel confirmation resend](https://docs.vexxhost.com/budgets/cli/budget-channel/budget-channel-confirmation-resend.md): Send an email channel's confirmation link again (once per 10 min).
- [openstack budget channel create](https://docs.vexxhost.com/budgets/cli/budget-channel/budget-channel-create.md): Create a webhook or email notification channel.
- [openstack budget channel delete](https://docs.vexxhost.com/budgets/cli/budget-channel/budget-channel-delete.md): Delete notification channels.
- [openstack budget channel list](https://docs.vexxhost.com/budgets/cli/budget-channel/budget-channel-list.md): List notification channels.
- [openstack budget channel secret rotate](https://docs.vexxhost.com/budgets/cli/budget-channel/budget-channel-secret-rotate.md): Replace a webhook channel's signing secret and show the new one.
- [openstack budget channel show](https://docs.vexxhost.com/budgets/cli/budget-channel/budget-channel-show.md): Show a notification channel.
- [openstack budget create](https://docs.vexxhost.com/budgets/cli/budget/budget-create.md): Create a budget with alert thresholds.
- [openstack budget delete](https://docs.vexxhost.com/budgets/cli/budget/budget-delete.md): Delete budgets.
- [openstack budget list](https://docs.vexxhost.com/budgets/cli/budget/budget-list.md): List budgets with their spend in the current window.
- [openstack budget set](https://docs.vexxhost.com/budgets/cli/budget/budget-set.md): Change a budget.
- [openstack budget show](https://docs.vexxhost.com/budgets/cli/budget/budget-show.md): Show a budget and the status of its current window.
- [openstack budget notification list](https://docs.vexxhost.com/budgets/cli/budget-notification/budget-notification-list.md): List the alerts a budget's thresholds raised, newest first.
- [openstack budget window list](https://docs.vexxhost.com/budgets/cli/budget-window/budget-window-list.md): List a budget's past and current windows, newest first.

## Platform

- [Limits](https://docs.vexxhost.com/budgets/platform/limits.md): The limits that apply to Budgets budgets, thresholds, notification channels and alert delivery.

## Optional

- [All Budgets pages in one file](https://docs.vexxhost.com/budgets/llms-full.txt)

URL: https://docs.vexxhost.com/budgets

---

# Get started

> Create your first budget with the OpenStack CLI, so that Budgets emails you when a project has spent 80% of its monthly amount.

This page takes you from nothing to a monthly budget that emails you when your project has spent
80% of it. It takes about five minutes, most of which is waiting for a confirmation email.

## Before you begin

You need:

* The `openstack` CLI, signed in to the project you want to watch. [Set up the CLI](/developer/cli)
  if you have not already.
* An email address you can read.

## 1. Install the Budgets commands

```sh
pip install vexxhost-budgetclient
```

## 2. Create an email channel

A notification channel is where alerts go. Create one for your email address:

```sh
openstack budget channel create finance --type email --address finance@example.com
```

```
+------------+--------------------------------------+
| Field      | Value                                |
+------------+--------------------------------------+
| id         | 3f9c1e7a-2b4d-4c6e-8a1f-5d7b9e2c4a6f |
| name       | finance                              |
| project_id | a6f1c2d93b8e4f07b5d4e2c1a9f8e7d6     |
| type       | email                                |
| status     | pending_confirmation                 |
| url        | None                                 |
| address    | finance@example.com                  |
| created_by | 4c8e2f1a9b7d4e6f8a3c5b1d7e9f2a4c     |
| created_at | 2026-10-07T14:02:11Z                 |
+------------+--------------------------------------+
```

Budgets sends a confirmation message to the address. Open it, follow the link, and select
**Confirm**. Until you do, the channel stays `pending_confirmation` and receives no alerts.

## 3. Create the budget

Create a monthly budget of 1000 with one threshold at 80% of actual spending, sent to the
channel you created:

```sh
openstack budget create team-monthly \
  --amount 1000 \
  --period monthly \
  --threshold name=warn,value=80,channel=finance \
  -c id -c name -c amount -c currency -c period
```

```
+----------+--------------------------------------+
| Field    | Value                                |
+----------+--------------------------------------+
| id       | c7e1a5b3-9d2f-4e8c-a6b4-1f3d5e7c9a2b |
| name     | team-monthly                         |
| amount   | 1000                                 |
| currency | USD                                  |
| period   | monthly                              |
+----------+--------------------------------------+
```

The currency is the one your costs are reported in, so you do not set it.

## 4. Check the budget

Budgets checks each budget about once an hour. After the first check, `budget show` reports what
the project has spent this month:

```sh
openstack budget show team-monthly -c window -c actual_spend -c threshold_states
```

```
+------------------+---------------------------------------------------------------------------+
| Field            | Value                                                                     |
+------------------+---------------------------------------------------------------------------+
| window           | 2026-10-01T00:00:00Z - 2026-11-01T00:00:00Z                               |
| actual_spend     | 248.731250000000                                                          |
| threshold_states | name='warn', observed='248.731250000000', state='ok', target_amount='800' |
+------------------+---------------------------------------------------------------------------+
```

The `warn` threshold is `ok` while spending stays under 800. When it reaches 800, the state
changes to `alarm` and Budgets emails `finance@example.com`.

## Next steps

* Add a threshold that warns when the month is forecast to go over budget. See
  [Thresholds](/budgets/concepts/thresholds).
* Send alerts to chat or your own systems with a [webhook](/budgets/guides/receive-webhook-alerts).
* Learn how periods, spending and alerts fit together in [Budgets concepts](/budgets/concepts/budgets).

URL: https://docs.vexxhost.com/budgets/get-started

---

# Budgets

> A budget is an amount a project may spend in each window of a period, with thresholds that decide when Budgets sends alerts.

A budget is an amount of money a project expects to spend in a period, such as 1000 a month. It
belongs to one project, it has one or more [thresholds](/budgets/concepts/thresholds) that decide when you hear
about spending, and it sends its alerts to [notification channels](/budgets/concepts/notification-channels).

A budget is not a spending cap. Budgets never stops, resizes or deletes a resource, and a project
can spend past its budget. To limit what a project can create, use its quotas.

## Periods

The budget's `period` decides how often its amount resets. Each repetition is a window, and every
window starts again from zero.

| Period      | Each window is                                                             |
| ----------- | -------------------------------------------------------------------------- |
| `daily`     | One calendar day                                                           |
| `monthly`   | One calendar month                                                         |
| `quarterly` | January to March, April to June, July to September, or October to December |
| `yearly`    | One calendar year                                                          |
| `custom`    | One fixed span you choose, up to 366 days, which does not repeat           |

Windows start and end at midnight UTC. A budget created part way through a month covers the whole
month, including spending from before it was created.

## What counts as spending

Budgets measures spending with [Cost Explorer](/cost-explorer), so a
budget counts the same estimated costs as your cost reports. Those costs are estimates before taxes,
credits, and discounts. See [Cost calculation](/cost-explorer/concepts/cost-calculation).

By default, everything in the project counts. A `filter` narrows the budget to some services
(`compute`, `volume`, `image`), regions, or usage types. For example, a budget with
`"filter": {"services": ["compute"]}` counts only servers.

A budget's amount is a decimal string, such as `"1000"` or `"1250.50"`, in the currency your
costs are reported in. Budgets fills it in for you, and
refuses any other currency.

## Status and history

Budgets checks every enabled budget about once an hour. Each check records the window's status:

| Field            | Meaning                                                                                 |
| ---------------- | --------------------------------------------------------------------------------------- |
| `actual_spend`   | What the project has spent in the window so far.                                        |
| `forecast_spend` | What the whole window is projected to cost, or `null` until a day of data is available. |
| `data_through`   | The time the spending is complete up to. Usage after it is not counted yet.             |
| `thresholds`     | Each threshold's `state`: `ok`, `alarm`, `unevaluated` or `disabled`.                   |

Reading a budget returns the status of its current window, or `null` before that window's first
check. Past windows and every alert sent, with its delivery status, stay available:

```sh
openstack budget window list team-monthly
openstack budget notification list team-monthly
```

Disabling a budget stops its checks and alerts without deleting it. Deleting a budget also deletes
its history.

## Access

Budgets and channels belong to a project. Your token's [project](/identity/projects)
decides which ones you see and change. Asking for another project's budgets returns `403 Forbidden`,
and asking for a budget in another project returns `404 Not Found`.

URL: https://docs.vexxhost.com/budgets/concepts/budgets

---

# Thresholds

> Thresholds decide when a budget sends alerts, based on actual spending so far or on the forecast for the whole window.

A threshold is a rule that sends an alert when a budget's spending crosses a level. Every budget
has between one and ten thresholds, and each one alerts its own notification channels.

## Actual and forecasted

A threshold's `basis` decides what it compares:

* `actual` compares what the project has spent in the window so far. It reports something that
  has already happened, such as "80% of the budget is spent."
* `forecasted` compares what the whole window is projected to cost. It warns you early, while
  there is still time to act, such as "this month is on course to cost more than the budget."

The forecast assumes the rest of the window continues at the rate spent so far. For example, if a
project spends 250 in the first 7 days of a 31-day month, the forecast for the month is about
1107\. A forecast cannot know about resources you are about to add or remove.

Forecasted thresholds wait until a day of the window has passed, and report `unevaluated` until
then. A daily budget is too short to forecast, so it cannot have forecasted thresholds.

## Levels

A threshold's `unit` and `value` set the level:

* `percentage` is a share of the budget's amount. A value of `80` on a 1000 budget alerts at 800.
* `absolute` is an amount of money. A value of `1200` alerts at 1200, whatever the budget's amount.

`comparison` decides which side of the level alerts. The default, `greater_than_or_equal`, alerts
when spending reaches the level. `greater_than` alerts only once spending exceeds it. `less_than` and
`less_than_or_equal` alert when spending is below the level, which is useful for spotting a
service that has stopped running.

A threshold can also carry a `message`, which every alert includes, such as who to contact.

## When alerts are sent

A threshold is `ok` until spending crosses its level, then it moves to `alarm` and sends one alert
to each of its channels.

* An actual threshold alerts once per window. It stays quiet however far past the level spending
  goes, so add a second, higher threshold if you also want to hear about that.
* A forecasted threshold can alert again in the same window. If the forecast drops back below the
  level and later crosses it again, it sends a new alert.
* Every threshold starts again at `ok` in each new window.
* Turning a threshold or its budget off and on again in the same window does not send the alert a
  second time.
* Renaming a threshold starts it again, because Budgets tracks thresholds by name.

## Examples

These thresholds on a 1000 monthly budget cover the common cases:

```json
[
  {"name": "half", "basis": "actual", "unit": "percentage", "value": "50", "channels": ["3f9c1e7a-2b4d-4c6e-8a1f-5d7b9e2c4a6f"]},
  {"name": "overrun-forecast", "basis": "forecasted", "unit": "percentage", "value": "100", "channels": ["3f9c1e7a-2b4d-4c6e-8a1f-5d7b9e2c4a6f"]},
  {"name": "over", "basis": "actual", "unit": "percentage", "value": "100", "channels": ["3f9c1e7a-2b4d-4c6e-8a1f-5d7b9e2c4a6f", "8b2d4f6a-1c3e-4a5b-9d7f-2e4c6a8b1d3f"]}
]
```

The first alerts when the month is half spent, the second warns early that it is heading over,
and the third alerts both channels when it has gone over.

URL: https://docs.vexxhost.com/budgets/concepts/thresholds

---

# Notification channels

> Notification channels are the email addresses and webhooks a project's budget alerts are sent to.

A notification channel is a destination for budget alerts: an email address or a webhook URL. A
channel belongs to a project, and any of that project's budgets can name it in a threshold.

## Alerts by email

An email channel sends each alert as a plain-text message. Before it receives any alert, its owner
must confirm the address. Budgets sends a message with a link, and the channel stays
`pending_confirmation` until someone follows it and selects **Confirm**. This stops anyone from
signing up an address that is not theirs.

The link expires after 72 hours. You can send a new one at most every 10 minutes:

```sh
openstack budget channel confirmation resend finance
```

Budgets does not send alerts to a channel that is not confirmed. It records them as failed
instead.

## Alerts by webhook

A webhook channel sends each alert as a signed JSON `POST` to an HTTPS URL. It is ready
immediately. The URL must be reachable from the internet, and Budgets does not follow redirects.

Budgets signs every request with the channel's secret, which Budgets shows only when you create the
channel or rotate the secret. Verify the signature before you act on an alert. See
[Receive alerts with a webhook](/budgets/guides/receive-webhook-alerts).

## Delivery

Budgets delivers each alert at least once. If a delivery fails, Budgets tries again with
increasing delays for about four hours, then records the alert as failed. A rare retry can deliver
an alert you already received, so each alert carries an `id` that stays the same across attempts.

Check what was sent, and why a delivery failed:

```sh
openstack budget notification list team-monthly
```

## Removing a channel

You cannot delete a channel while a budget's threshold still names it. The API returns
`409 Conflict`. Remove the channel from the budget's thresholds first, then delete it.

URL: https://docs.vexxhost.com/budgets/concepts/notification-channels

---

# Create a budget

> Create a Budgets budget with the CLI, the API or Python, choosing its period, the spending it counts and the thresholds that send alerts.

This guide creates a monthly budget of 1000 for compute spending, with one threshold that alerts
when 80% is spent and another that warns when the month is forecast to go over. For a shorter
first run, see [Get started](/budgets/get-started).

## Before you begin

* Sign in with the [CLI](/developer/cli), get a
  [token](/identity/get-a-token) for the API, or create the
  [Python client](/budgets/guides/use-the-python-client).
* Create at least one confirmed [notification channel](/budgets/concepts/notification-channels). The
  CLI refers to channels by name. The API and Python client use their IDs, which
  `openstack budget channel list` shows.

## 1. Choose the period and amount

Pick a `period` of `daily`, `monthly`, `quarterly` or `yearly` for a budget that resets, or
`custom` for one fixed span. The `amount` is per window and is a decimal string in the API.
For a custom period, also set its start and end:

```sh
openstack budget create launch-quarter \
  --amount 5000 \
  --period custom \
  --start 2026-11-01 \
  --end 2027-02-01 \
  --threshold name=warn,value=80,channel=finance
```

## 2. Choose what counts

Leave out the filter to count everything in the project. To count only some spending, filter by
service (`compute`, `volume`, `image`), region, or usage type. This guide counts compute only.

## 3. Create the budget with its thresholds

A budget needs between one and ten [thresholds](/budgets/concepts/thresholds). Each names a level, what it
compares, and the channels it alerts.

<CodeBlockTabs defaultValue="CLI">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="CLI">
      CLI
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="API">
      API
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="Python">
      Python
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="CLI">
    ```sh
    openstack budget create compute-monthly \
      --amount 1000 \
      --period monthly \
      --service compute \
      --threshold name=warn,value=80,channel=finance \
      --threshold name=overrun,value=100,basis=forecasted,channel=finance
    ```
  </CodeBlockTab>

  <CodeBlockTab value="API">
    ```sh
    curl --request POST https://budget.public.global.vexxhost.net/v1/budgets \
      --header "X-Auth-Token: $TOKEN" \
      --header "Content-Type: application/json" \
      --data '{
        "name": "compute-monthly",
        "amount": "1000",
        "period": "monthly",
        "filter": {"services": ["compute"]},
        "thresholds": [
          {"name": "warn", "basis": "actual", "unit": "percentage", "value": "80",
           "channels": ["3f9c1e7a-2b4d-4c6e-8a1f-5d7b9e2c4a6f"]},
          {"name": "overrun", "basis": "forecasted", "unit": "percentage", "value": "100",
           "channels": ["3f9c1e7a-2b4d-4c6e-8a1f-5d7b9e2c4a6f"]}
        ]
      }'
    ```
  </CodeBlockTab>

  <CodeBlockTab value="Python">
    ```python
    budget = budgets.budgets.create(
        name="compute-monthly",
        amount="1000",
        period="monthly",
        filter={"services": ["compute"]},
        thresholds=[
            {"name": "warn", "basis": "actual", "unit": "percentage", "value": "80",
             "channels": ["3f9c1e7a-2b4d-4c6e-8a1f-5d7b9e2c4a6f"]},
            {"name": "overrun", "basis": "forecasted", "unit": "percentage", "value": "100",
             "channels": ["3f9c1e7a-2b4d-4c6e-8a1f-5d7b9e2c4a6f"]},
        ],
    )
    ```
  </CodeBlockTab>
</CodeBlockTabs>

On the CLI, `basis` defaults to `actual` and `unit` to `percentage`. The API and Python client
need both fields on every threshold.

The API answers `201 Created` with the budget:

```json
{
  "id": "c7e1a5b3-9d2f-4e8c-a6b4-1f3d5e7c9a2b",
  "name": "compute-monthly",
  "project_id": "a6f1c2d93b8e4f07b5d4e2c1a9f8e7d6",
  "enabled": true,
  "amount": "1000",
  "currency": "USD",
  "period": "monthly",
  "filter": {"services": ["compute"]},
  "thresholds": [
    {"name": "warn", "basis": "actual", "unit": "percentage", "value": "80",
     "comparison": "greater_than_or_equal", "enabled": true,
     "channels": ["3f9c1e7a-2b4d-4c6e-8a1f-5d7b9e2c4a6f"]},
    {"name": "overrun", "basis": "forecasted", "unit": "percentage", "value": "100",
     "comparison": "greater_than_or_equal", "enabled": true,
     "channels": ["3f9c1e7a-2b4d-4c6e-8a1f-5d7b9e2c4a6f"]}
  ],
  "revision": 1,
  "created_by": "4c8e2f1a9b7d4e6f8a3c5b1d7e9f2a4c",
  "created_at": "2026-10-07T14:10:27Z",
  "updated_at": "2026-10-07T14:10:27Z",
  "status": null
}
```

`status` is `null` until Budgets first checks the budget, within about an hour.

## 4. Verify

After the first check, the budget reports its spending and each threshold's state:

```sh
openstack budget list
```

```
+--------------------------------------+-----------------+----------------------------------+---------+--------+----------+------------------+-------------------+---------+---------+
| ID                                   | Name            | Project ID                       | Period  | Amount | Currency | Actual Spend     | Forecast Spend    | Alarms  | Enabled |
+--------------------------------------+-----------------+----------------------------------+---------+--------+----------+------------------+-------------------+---------+---------+
| c7e1a5b3-9d2f-4e8c-a6b4-1f3d5e7c9a2b | compute-monthly | a6f1c2d93b8e4f07b5d4e2c1a9f8e7d6 | monthly | 1000   | USD      | 248.731250000000 | 1155.164285714286 | overrun | True    |
+--------------------------------------+-----------------+----------------------------------+---------+--------+----------+------------------+-------------------+---------+---------+
```

Here the project has spent about 249 of its 1000, but the month is forecast to cost about 1155,
so the `overrun` threshold is in alarm and has alerted the `finance` channel.

## Next steps

* [Receive alerts with a webhook](/budgets/guides/receive-webhook-alerts) to send alerts to chat or your own
  systems.
* [Change a budget](/budgets/guides/change-a-budget) when the amount or thresholds need adjusting.
* Look up every option in the [CLI reference](/budgets/cli) or the
  [API reference](/budgets/api).

URL: https://docs.vexxhost.com/budgets/guides/create-a-budget

---

# Receive alerts with a webhook

> Send Budgets alerts to your own HTTPS endpoint as signed JSON, verify each request, and handle retries.

This guide sends a budget's alerts to an HTTPS endpoint you run, such as a chat integration, or an
on-call system, and shows how to check that each request really comes from Budgets.

## Before you begin

* An HTTPS URL reachable from the internet that accepts `POST` requests. Budgets does not follow
  redirects.
* A budget to send alerts from. See [Create a budget](/budgets/guides/create-a-budget).

## 1. Create the webhook channel

<CodeBlockTabs defaultValue="CLI">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="CLI">
      CLI
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="API">
      API
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="CLI">
    ```sh
    openstack budget channel create ops --type webhook --url https://hooks.example.com/budget-alerts
    ```
  </CodeBlockTab>

  <CodeBlockTab value="API">
    ```sh
    curl --request POST https://budget.public.global.vexxhost.net/v1/notification_channels \
      --header "X-Auth-Token: $TOKEN" \
      --header "Content-Type: application/json" \
      --data '{"name": "ops", "type": "webhook", "url": "https://hooks.example.com/budget-alerts"}'
    ```
  </CodeBlockTab>
</CodeBlockTabs>

```
+------------+---------------------------------------------------+
| Field      | Value                                             |
+------------+---------------------------------------------------+
| id         | 8b2d4f6a-1c3e-4a5b-9d7f-2e4c6a8b1d3f              |
| name       | ops                                               |
| project_id | a6f1c2d93b8e4f07b5d4e2c1a9f8e7d6                  |
| type       | webhook                                           |
| status     | confirmed                                         |
| url        | https://hooks.example.com/budget-alerts           |
| address    | None                                              |
| secret     | whsec_Q2l4b0VyQ2pVd1Rsb3hNcUpwWGZ6Y1RnS3ZOYkhhRWQ |
| created_by | 4c8e2f1a9b7d4e6f8a3c5b1d7e9f2a4c                  |
| created_at | 2026-10-07T14:05:43Z                              |
+------------+---------------------------------------------------+
```

Store the `secret` now. Budgets shows it only when you create the channel or rotate its secret,
and your endpoint needs it to verify requests.

## 2. Send a budget's alerts to it

Add the channel to the thresholds that should use it. On the CLI, `--threshold` replaces all of a
budget's thresholds, so list every one you want to keep:

```sh
openstack budget set compute-monthly \
  --threshold name=warn,value=80,channel=finance,channel=ops \
  --threshold name=overrun,value=100,basis=forecasted,channel=finance,channel=ops
```

## 3. Handle the request

Each alert arrives as a `POST` with a JSON body:

```http
POST /budget-alerts HTTP/1.1
Host: hooks.example.com
Content-Type: application/json
User-Agent: vexxhost-budget
X-Budget-Signature: t=1792854007,v1=4b7e0f2c9a1d6e3b8f5c2a7d4e1b9c6f3a8d5e2b7c4f1a9e6d3b0c8f5a2e7d4b
```

```json
{
  "id": "5e9a3c1f-7b2d-4f6e-9c8a-1d3b5f7e9a2c",
  "type": "budget.threshold.alarm",
  "text": "Budget \"compute-monthly\": spend 812.40 has reached 800.00 USD (threshold \"warn\")",
  "budget_id": "c7e1a5b3-9d2f-4e8c-a6b4-1f3d5e7c9a2b",
  "budget_name": "compute-monthly",
  "project_id": "a6f1c2d93b8e4f07b5d4e2c1a9f8e7d6",
  "threshold": {
    "name": "warn",
    "basis": "actual",
    "comparison": "greater_than_or_equal",
    "message": "Ask in #infra before adding servers."
  },
  "time_period": {"start": "2026-10-01T00:00:00Z", "end": "2026-11-01T00:00:00Z"},
  "currency": "USD",
  "amount": "1000",
  "target": "800",
  "observed": "812.395",
  "as_of": "2026-10-24T14:00:00Z"
}
```

Budgets sends the body as compact JSON. It is formatted here for reading.

| Field         | Meaning                                                                           |
| ------------- | --------------------------------------------------------------------------------- |
| `id`          | The alert's ID. It stays the same on every delivery attempt.                      |
| `type`        | Always `budget.threshold.alarm`.                                                  |
| `text`        | A one-line summary, ready to post to chat.                                        |
| `threshold`   | The threshold that crossed, with its optional `message`.                          |
| `time_period` | The budget window the alert is about.                                             |
| `amount`      | The budget's amount for the window.                                               |
| `target`      | The threshold's level as an amount.                                               |
| `observed`    | The spending, or for a forecasted threshold the forecast, that crossed the level. |
| `as_of`       | When the spending was measured.                                                   |

Answer with any `2xx` status within 10 seconds. Budgets retries any other answer, or no answer,
with increasing delays for about four hours. Because a retry can repeat an alert you already
handled, ignore an `id` you have seen before.

## 4. Verify the signature

Check every request before you act on it. `X-Budget-Signature` holds a Unix timestamp `t` and `v1`,
the hex-encoded HMAC-SHA256 of the timestamp, a period and the raw request body, keyed with the
channel's secret. Reject the request if the signature does not match, or if `t` is more than five
minutes from your clock. Compute the signature over the body exactly as it arrived, before you
parse it.

<CodeBlockTabs defaultValue="Python">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="Python">
      Python
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="Node.js">
      Node.js
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="Go">
      Go
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="Python">
    ```python
    import hashlib
    import hmac
    import time

    def verify(secret: str, header: str, body: bytes, tolerance: int = 300) -> bool:
        parts = dict(item.split("=", 1) for item in header.split(","))
        timestamp, signature = parts.get("t", ""), parts.get("v1", "")
        if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > tolerance:
            return False
        expected = hmac.new(secret.encode(), f"{timestamp}.".encode() + body, hashlib.sha256).hexdigest()
        return hmac.compare_digest(expected, signature)
    ```
  </CodeBlockTab>

  <CodeBlockTab value="Node.js">
    ```js
    import { createHmac, timingSafeEqual } from "node:crypto";

    export function verify(secret, header, body, tolerance = 300) {
      const parts = Object.fromEntries(header.split(",").map((p) => p.split("=", 2)));
      const timestamp = Number(parts.t);
      if (!Number.isInteger(timestamp) || Math.abs(Date.now() / 1000 - timestamp) > tolerance) return false;
      const expected = createHmac("sha256", secret).update(`${parts.t}.`).update(body).digest("hex");
      const given = Buffer.from(parts.v1 ?? "", "utf8");
      return given.length === expected.length && timingSafeEqual(given, Buffer.from(expected, "utf8"));
    }
    ```
  </CodeBlockTab>

  <CodeBlockTab value="Go">
    ```go
    func verify(secret, header string, body []byte, tolerance time.Duration) bool {
    	var timestamp, signature string
    	for _, part := range strings.Split(header, ",") {
    		key, value, _ := strings.Cut(part, "=")
    		switch key {
    		case "t":
    			timestamp = value
    		case "v1":
    			signature = value
    		}
    	}
    	t, err := strconv.ParseInt(timestamp, 10, 64)
    	if err != nil || time.Since(time.Unix(t, 0)).Abs() > tolerance {
    		return false
    	}
    	mac := hmac.New(sha256.New, []byte(secret))
    	mac.Write([]byte(timestamp + "."))
    	mac.Write(body)
    	return hmac.Equal([]byte(hex.EncodeToString(mac.Sum(nil))), []byte(signature))
    }
    ```
  </CodeBlockTab>
</CodeBlockTabs>

## Verify

Ask Budgets which alerts it sent and whether your endpoint accepted them:

```sh
openstack budget notification list compute-monthly
```

Each alert is `pending` while Budgets is still trying, `sent` once your endpoint answered with a
`2xx` status, or `failed` with the last error once it stops trying.

## Rotate the secret

Replace the secret if it might have leaked:

```sh
openstack budget channel secret rotate ops
```

The new secret replaces the old one immediately, so update your endpoint at the same time.

## Next steps

* Read how [notification channels](/budgets/concepts/notification-channels) deliver alerts.
* Look up the channel operations in the [API reference](/budgets/api).

URL: https://docs.vexxhost.com/budgets/guides/receive-webhook-alerts

---

# Change a budget

> Change a Budgets budget's amount, filter or thresholds, pause it, or delete it, without overwriting someone else's change.

This guide raises a budget's amount, pauses it, and deletes it. It also explains what happens when
two people change the same budget at once.

## Before you begin

You need a budget to change. See [Create a budget](/budgets/guides/create-a-budget). For the Python examples, create the
[Python client](/budgets/guides/use-the-python-client).

## 1. Change the amount or thresholds

The CLI changes only the options you give it and keeps the rest:

```sh
openstack budget set compute-monthly --amount 1500
```

`--threshold` replaces all of the budget's thresholds, so repeat every threshold you want to keep.
`--no-service`, `--no-region` and `--no-usage-type` clear a filter.

The API and Python client replace the whole budget, and the request carries the `revision` you
read. Read the budget, change it, and write it back:

<CodeBlockTabs defaultValue="Python">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="Python">
      Python
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="API">
      API
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="Python">
    ```python
    from budgetclient.v1.budgets import to_update_spec

    current = budgets.budgets.find("compute-monthly")
    spec = to_update_spec(current)
    spec["amount"] = "1500"
    budgets.budgets.update(current["id"], **spec)
    ```
  </CodeBlockTab>

  <CodeBlockTab value="API">
    ```sh
    curl --request PUT https://budget.public.global.vexxhost.net/v1/budgets/c7e1a5b3-9d2f-4e8c-a6b4-1f3d5e7c9a2b \
      --header "X-Auth-Token: $TOKEN" \
      --header "Content-Type: application/json" \
      --data '{
        "revision": 1,
        "name": "compute-monthly",
        "amount": "1500",
        "period": "monthly",
        "filter": {"services": ["compute"]},
        "thresholds": [
          {"name": "warn", "basis": "actual", "unit": "percentage", "value": "80",
           "channels": ["3f9c1e7a-2b4d-4c6e-8a1f-5d7b9e2c4a6f"]},
          {"name": "overrun", "basis": "forecasted", "unit": "percentage", "value": "100",
           "channels": ["3f9c1e7a-2b4d-4c6e-8a1f-5d7b9e2c4a6f"]}
        ]
      }'
    ```
  </CodeBlockTab>
</CodeBlockTabs>

If someone changed the budget after you read it, the API refuses the update with `409 Conflict`
instead of overwriting their change. Read the budget again and reapply yours. The CLI reads the
budget for you, so this only happens when two changes land within moments of each other.

## 2. Pause or resume it

A budget that is turned off is not checked and sends no alerts, but keeps its settings and
history:

```sh
openstack budget set compute-monthly --disable
openstack budget set compute-monthly --enable
```

Turning a budget off and on again in the same window does not repeat an alert it already sent.

## 3. Delete it

Deleting a budget also deletes its window and alert history:

```sh
openstack budget delete compute-monthly
```

## Verify

```sh
openstack budget show compute-monthly -c amount -c enabled -c revision
```

```
+----------+-------+
| Field    | Value |
+----------+-------+
| amount   | 1500  |
| enabled  | True  |
| revision | 2     |
+----------+-------+
```

Each change increases `revision` by one.

## Next steps

* Review what a budget has sent with `openstack budget notification list compute-monthly`.
* See every option of `budget set` in the [CLI reference](/budgets/cli/budget/budget-set).

URL: https://docs.vexxhost.com/budgets/guides/change-a-budget

---

# Use the Python client

> Install the Budgets Python client, create it from an OpenStack SDK connection, and handle the errors it raises.

The `vexxhost-budgetclient` package is the Python client for Budgets. It signs in with the same session as the
OpenStack SDK, so it works with your existing `clouds.yaml`.

## Before you begin

Set up a `clouds.yaml` entry or an application credential. See
[Use OpenStack from Python](/developer/python).

## 1. Install the client

```sh
pip install vexxhost-budgetclient
```

## 2. Create the client

```python
import openstack
from budgetclient import Client

conn = openstack.connect(cloud="vexxhost")
budgets = Client("1", session=conn.session)
```

The client finds Budgets in the [service catalog](/identity/service-catalog).
The other Budgets guides call it `budgets`.

## 3. Make a call

Methods take and return dictionaries with the same fields as the API's JSON, so the
[API reference](/budgets/api) also describes each call. `budgets.budgets` manages budgets and `budgets.notification_channels` manages channels.

```python
for budget in budgets.budgets.list():
    print(budget["name"], budget["amount"], budget["currency"])
```

## 4. Handle errors

A failed request raises an exception from `budgetclient.exceptions`, such as `Conflict`,
`TooManyRequests` or `Unauthorized`. The exceptions are also `keystoneauth1.exceptions` HTTP errors.

| Attribute     | Contents                                                                 |
| ------------- | ------------------------------------------------------------------------ |
| `status_code` | The HTTP status.                                                         |
| `problem`     | The API's error document, with `title`, `detail` and any field `errors`. |
| `request_id`  | The `X-Request-ID` of the request, for support.                          |
| `retry_after` | Seconds to wait before retrying, on `429 Too Many Requests`.             |

```python
from budgetclient import exceptions

try:
    budgets.budgets.get("c7e1a5b3-9d2f-4e8c-a6b4-1f3d5e7c9a2b")
except exceptions.ClientException as error:
    print(error.status_code, error.request_id, error.problem["detail"])
    raise
```

## Next steps

* Use `budgets` in the Python examples of the other Budgets guides.

URL: https://docs.vexxhost.com/budgets/guides/use-the-python-client

---

# Budgets API

> Call the Budgets API: base URL, authentication, a first request, and every operation.

The Budgets API is a JSON API over HTTPS. Spending limits for projects, with alerts by email or webhook when spend or its forecast crosses a threshold.

## Base URL

```
https://budget.public.global.vexxhost.net
```

## Authentication

Send a token from [Identity](/identity/authentication) in the `X-Auth-Token` header with every request. See [Make API requests](/developer/api) for status codes and retries.

## Make your first request

List your project's budgets:

```sh
curl https://budget.public.global.vexxhost.net/v1/budgets \
  --header "X-Auth-Token: $TOKEN"
```

```json
{
  "budgets": [
    {
      "id": "c7e1a5b3-9d2f-4e8c-a6b4-1f3d5e7c9a2b",
      "name": "compute-monthly",
      "project_id": "a6f1c2d93b8e4f07b5d4e2c1a9f8e7d6",
      "enabled": true,
      "amount": "1000",
      "currency": "USD",
      "period": "monthly",
      "filter": {"services": ["compute"]},
      "thresholds": [
        {"name": "warn", "basis": "actual", "unit": "percentage", "value": "80",
         "comparison": "greater_than_or_equal", "enabled": true,
         "channels": ["3f9c1e7a-2b4d-4c6e-8a1f-5d7b9e2c4a6f"]}
      ],
      "revision": 1,
      "created_by": "4c8e2f1a9b7d4e6f8a3c5b1d7e9f2a4c",
      "created_at": "2026-10-07T14:10:27Z",
      "updated_at": "2026-10-07T14:10:27Z",
      "status": {
        "time_period": {"start": "2026-10-01T00:00:00Z", "end": "2026-11-01T00:00:00Z"},
        "evaluated_at": "2026-10-07T15:00:04Z",
        "as_of": "2026-10-07T15:00:00Z",
        "data_through": "2026-10-07T14:00:00Z",
        "currency": "USD",
        "amount": "1000",
        "actual_spend": "248.731250000000",
        "forecast_spend": "1155.164285714286",
        "estimated": true,
        "thresholds": [
          {"name": "warn", "state": "ok", "target_amount": "800", "observed": "248.731250000000"}
        ]
      }
    }
  ]
}
```

Budgets and notification channels are the two resources. A budget's thresholds name channels by
ID, so create channels first.

## Errors

Errors use the [problem details](https://www.rfc-editor.org/rfc/rfc9457) format, `application/problem+json`. `detail` says what went wrong, and a validation error lists each problem in `errors` with the `location` of the field:

```json
{
  "status": 422,
  "title": "Unprocessable Entity",
  "detail": "validation failed",
  "errors": [
    {
      "location": "body",
      "message": "expected required property name to be present",
      "value": {}
    }
  ]
}
```

Every response carries an `X-Request-ID` header. Include it when you contact support about a request.

## Operations

| Method   | Path                                                                                                                           | Description                                             |
| -------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------- |
| `GET`    | [`/v1/budgets`](/budgets/api/budgets/list-budgets)                                                                             | List budgets                                            |
| `POST`   | [`/v1/budgets`](/budgets/api/budgets/create-budget)                                                                            | Create a budget                                         |
| `GET`    | [`/v1/budgets/{budget_id}`](/budgets/api/budgets/get-budget)                                                                   | Get a budget and the status of its current window       |
| `PUT`    | [`/v1/budgets/{budget_id}`](/budgets/api/budgets/update-budget)                                                                | Replace a budget                                        |
| `DELETE` | [`/v1/budgets/{budget_id}`](/budgets/api/budgets/delete-budget)                                                                | Delete a budget and its history                         |
| `GET`    | [`/v1/budgets/{budget_id}/notifications`](/budgets/api/budgets/list-budget-notifications)                                      | List a budget's alerts and their delivery, newest first |
| `GET`    | [`/v1/budgets/{budget_id}/windows`](/budgets/api/budgets/list-budget-windows)                                                  | List a budget's past and current windows, newest first  |
| `GET`    | [`/v1/notification_channels`](/budgets/api/notification-channels/list-notification-channels)                                   | List notification channels                              |
| `POST`   | [`/v1/notification_channels`](/budgets/api/notification-channels/create-notification-channel)                                  | Create a notification channel                           |
| `GET`    | [`/v1/notification_channels/{channel_id}`](/budgets/api/notification-channels/get-notification-channel)                        | Get a notification channel                              |
| `DELETE` | [`/v1/notification_channels/{channel_id}`](/budgets/api/notification-channels/delete-notification-channel)                     | Delete a notification channel                           |
| `POST`   | [`/v1/notification_channels/{channel_id}/resend_confirmation`](/budgets/api/notification-channels/resend-channel-confirmation) | Send an email channel's confirmation link again         |
| `POST`   | [`/v1/notification_channels/{channel_id}/rotate_secret`](/budgets/api/notification-channels/rotate-channel-secret)             | Replace a webhook channel's signing secret              |

## OpenAPI schema

Download the schema as [JSON](/budgets/openapi.json) or [YAML](/budgets/openapi.yaml) to generate a client or import the API into a tool such as Postman.

URL: https://docs.vexxhost.com/budgets/api

---

# List budgets

`GET https://budget.public.global.vexxhost.net/v1/budgets`

Lists the budgets of your token's project, each with the status of its current window.

Authentication: `X-Auth-Token: <token>` header. See https://docs.vexxhost.com/identity/authentication.md.

## Parameters

- `project_id` (query, string): The project whose budgets to list. Defaults to your token's project; naming another project is refused with 403.

## Responses

### 200 — OK

`application/json`:

- `budgets` (array of object, nullable, required)
  - `amount` (string, required)
  - `created_at` (string, date-time, required)
  - `created_by` (string, required)
  - `currency` (string, required)
  - `custom_period` (object)
    - `end` (string, date-time, required): Exclusive end (RFC 3339).
    - `start` (string, date-time, required): Inclusive start (RFC 3339).
  - `enabled` (boolean, required)
  - `filter` (object, required)
    - `regions` (array of string, nullable): Regions to count. Empty counts every region.
    - `services` (array of string, one of `compute`, `image`, `volume`, nullable): Services to count. Empty counts every service.
    - `usage_types` (array of string, nullable): Usage types to count. Empty counts every usage type.
  - `id` (string, required)
  - `name` (string, required)
  - `period` (string, required)
  - `project_id` (string, required)
  - `revision` (integer, int64, required)
  - `status` (object, required): Spend and threshold states for the current window as of its latest check, or null before the window's first check.
    - `actual_spend` (string, required)
    - `amount` (string, required): The budget amount the thresholds were judged against.
    - `as_of` (string, date-time, required): When the spend was measured.
    - `currency` (string, required)
    - `data_through` (string, date-time, required): How far the usage data is complete. Spend after this is not counted yet.
    - `estimated` (boolean, required): Always true: costs are computed from usage and a price catalogue.
    - `evaluated_at` (string, date-time, required)
    - `forecast_spend` (string, nullable, required): Linear projection for the whole window; null until enough of it has elapsed.
    - `thresholds` (array of object, nullable, required)
      - `changed_at` (string, date-time): When the threshold last entered or left alarm in this window.
      - `name` (string, required)
      - `observed` (string, nullable, required)
      - `state` (string, one of `ok`, `alarm`, `unevaluated`, `disabled`, required)
      - `target_amount` (string, required)
    - `time_period` (object, required)
      - `end` (string, date-time, required): Exclusive end (RFC 3339).
      - `start` (string, date-time, required): Inclusive start (RFC 3339).
  - `thresholds` (array of object, nullable, required)
    - `basis` (string, required)
    - `channels` (array of string, nullable, required)
    - `comparison` (string, required)
    - `enabled` (boolean, required)
    - `message` (string)
    - `name` (string, required)
    - `unit` (string, required)
    - `value` (string, required)
  - `updated_at` (string, date-time, required)

### 400 — Bad Request

`application/problem+json`:

- `detail` (string): A human-readable explanation specific to this occurrence of the problem. Example: `Property foo is required but is missing.`.
- `errors` (array of object, nullable): Optional list of individual error details
  - `location` (string): Where the error occurred, e.g. 'body.items[3].tags' or 'path.thing-id'
  - `message` (string): Error message text
  - `value` (object): The value at the given location
- `instance` (string, uri): A URI reference that identifies the specific occurrence of the problem. Example: `https://example.com/error-log/abc123`.
- `status` (integer, int64): HTTP status code Example: `400`.
- `title` (string): A short, human-readable summary of the problem type. This value should not change between occurrences of the error. Example: `Bad Request`.
- `type` (string, uri): A URI reference to human-readable documentation for the error. Example: `https://example.com/errors/example`.

### 401 — Unauthorized

Same `application/problem+json` body as 400.

### 403 — Forbidden

Same `application/problem+json` body as 400.

### 404 — Not Found

Same `application/problem+json` body as 400.

### 409 — Conflict

Same `application/problem+json` body as 400.

### 422 — Unprocessable Entity

Same `application/problem+json` body as 400.

### 500 — Internal Server Error

Same `application/problem+json` body as 400.

### 503 — Service Unavailable

Same `application/problem+json` body as 400.

Full schema: https://docs.vexxhost.com/budgets/openapi.json

URL: https://docs.vexxhost.com/budgets/api/budgets/list-budgets

---

# Create a budget

`POST https://budget.public.global.vexxhost.net/v1/budgets`

Authentication: `X-Auth-Token: <token>` header. See https://docs.vexxhost.com/identity/authentication.md.

## Request body (application/json)

- `amount` (string, required): Budgeted amount per window, as a decimal string.
- `currency` (string): Defaults to, and must equal, the currency your costs are reported in.
- `custom_period` (object): Required when period is custom, at most 366 days.
  - `end` (string, date-time, required): Exclusive end (RFC 3339).
  - `start` (string, date-time, required): Inclusive start (RFC 3339).
- `enabled` (boolean): Defaults to true. A disabled budget is not checked and sends no alerts.
- `filter` (object)
  - `regions` (array of string, nullable): Regions to count. Empty counts every region.
  - `services` (array of string, one of `compute`, `image`, `volume`, nullable): Services to count. Empty counts every service.
  - `usage_types` (array of string, nullable): Usage types to count. Empty counts every usage type.
- `name` (string, required)
- `period` (string, one of `daily`, `monthly`, `quarterly`, `yearly`, `custom`, required): Calendar periods reset in UTC; custom is one fixed window.
- `project_id` (string): The project the budget belongs to and measures. Omitted, it is the caller's own project.
- `thresholds` (array of object, nullable, required)
  - `basis` (string, one of `actual`, `forecasted`, required): Judge spend so far (actual) or the projection for the whole window (forecasted).
  - `channels` (array of string, nullable): Configured notification channels to alert when the threshold enters alarm. With none the threshold still tracks its state.
  - `comparison` (string, one of `greater_than`, `greater_than_or_equal`, `less_than`, `less_than_or_equal`): Defaults to greater_than_or_equal.
  - `enabled` (boolean): Defaults to true.
  - `message` (string): Text included in the alert.
  - `name` (string, required): Unique within the budget. State is tracked by name, so renaming a threshold restarts it.
  - `unit` (string, one of `percentage`, `absolute`, required): Whether value is a percentage of the budget amount or an amount in its currency.
  - `value` (string, required): Decimal string.

## Responses

### 201 — Created

`application/json`:

- `amount` (string, required)
- `created_at` (string, date-time, required)
- `created_by` (string, required)
- `currency` (string, required)
- `custom_period` (object)
  - `end` (string, date-time, required): Exclusive end (RFC 3339).
  - `start` (string, date-time, required): Inclusive start (RFC 3339).
- `enabled` (boolean, required)
- `filter` (object, required)
  - `regions` (array of string, nullable): Regions to count. Empty counts every region.
  - `services` (array of string, one of `compute`, `image`, `volume`, nullable): Services to count. Empty counts every service.
  - `usage_types` (array of string, nullable): Usage types to count. Empty counts every usage type.
- `id` (string, required)
- `name` (string, required)
- `period` (string, required)
- `project_id` (string, required)
- `revision` (integer, int64, required)
- `status` (object, required): Spend and threshold states for the current window as of its latest check, or null before the window's first check.
  - `actual_spend` (string, required)
  - `amount` (string, required): The budget amount the thresholds were judged against.
  - `as_of` (string, date-time, required): When the spend was measured.
  - `currency` (string, required)
  - `data_through` (string, date-time, required): How far the usage data is complete. Spend after this is not counted yet.
  - `estimated` (boolean, required): Always true: costs are computed from usage and a price catalogue.
  - `evaluated_at` (string, date-time, required)
  - `forecast_spend` (string, nullable, required): Linear projection for the whole window; null until enough of it has elapsed.
  - `thresholds` (array of object, nullable, required)
    - `changed_at` (string, date-time): When the threshold last entered or left alarm in this window.
    - `name` (string, required)
    - `observed` (string, nullable, required)
    - `state` (string, one of `ok`, `alarm`, `unevaluated`, `disabled`, required)
    - `target_amount` (string, required)
  - `time_period` (object, required)
    - `end` (string, date-time, required): Exclusive end (RFC 3339).
    - `start` (string, date-time, required): Inclusive start (RFC 3339).
- `thresholds` (array of object, nullable, required)
  - `basis` (string, required)
  - `channels` (array of string, nullable, required)
  - `comparison` (string, required)
  - `enabled` (boolean, required)
  - `message` (string)
  - `name` (string, required)
  - `unit` (string, required)
  - `value` (string, required)
- `updated_at` (string, date-time, required)

### 400 — Bad Request

`application/problem+json`:

- `detail` (string): A human-readable explanation specific to this occurrence of the problem. Example: `Property foo is required but is missing.`.
- `errors` (array of object, nullable): Optional list of individual error details
  - `location` (string): Where the error occurred, e.g. 'body.items[3].tags' or 'path.thing-id'
  - `message` (string): Error message text
  - `value` (object): The value at the given location
- `instance` (string, uri): A URI reference that identifies the specific occurrence of the problem. Example: `https://example.com/error-log/abc123`.
- `status` (integer, int64): HTTP status code Example: `400`.
- `title` (string): A short, human-readable summary of the problem type. This value should not change between occurrences of the error. Example: `Bad Request`.
- `type` (string, uri): A URI reference to human-readable documentation for the error. Example: `https://example.com/errors/example`.

### 401 — Unauthorized

Same `application/problem+json` body as 400.

### 403 — Forbidden

Same `application/problem+json` body as 400.

### 404 — Not Found

Same `application/problem+json` body as 400.

### 409 — Conflict

Same `application/problem+json` body as 400.

### 422 — Unprocessable Entity

Same `application/problem+json` body as 400.

### 500 — Internal Server Error

Same `application/problem+json` body as 400.

### 503 — Service Unavailable

Same `application/problem+json` body as 400.

Full schema: https://docs.vexxhost.com/budgets/openapi.json

URL: https://docs.vexxhost.com/budgets/api/budgets/create-budget

---

# Get a budget and the status of its current window

`GET https://budget.public.global.vexxhost.net/v1/budgets/{budget_id}`

Authentication: `X-Auth-Token: <token>` header. See https://docs.vexxhost.com/identity/authentication.md.

## Parameters

- `budget_id` (path, string, required): Budget identifier.

## Responses

### 200 — OK

`application/json`:

- `amount` (string, required)
- `created_at` (string, date-time, required)
- `created_by` (string, required)
- `currency` (string, required)
- `custom_period` (object)
  - `end` (string, date-time, required): Exclusive end (RFC 3339).
  - `start` (string, date-time, required): Inclusive start (RFC 3339).
- `enabled` (boolean, required)
- `filter` (object, required)
  - `regions` (array of string, nullable): Regions to count. Empty counts every region.
  - `services` (array of string, one of `compute`, `image`, `volume`, nullable): Services to count. Empty counts every service.
  - `usage_types` (array of string, nullable): Usage types to count. Empty counts every usage type.
- `id` (string, required)
- `name` (string, required)
- `period` (string, required)
- `project_id` (string, required)
- `revision` (integer, int64, required)
- `status` (object, required): Spend and threshold states for the current window as of its latest check, or null before the window's first check.
  - `actual_spend` (string, required)
  - `amount` (string, required): The budget amount the thresholds were judged against.
  - `as_of` (string, date-time, required): When the spend was measured.
  - `currency` (string, required)
  - `data_through` (string, date-time, required): How far the usage data is complete. Spend after this is not counted yet.
  - `estimated` (boolean, required): Always true: costs are computed from usage and a price catalogue.
  - `evaluated_at` (string, date-time, required)
  - `forecast_spend` (string, nullable, required): Linear projection for the whole window; null until enough of it has elapsed.
  - `thresholds` (array of object, nullable, required)
    - `changed_at` (string, date-time): When the threshold last entered or left alarm in this window.
    - `name` (string, required)
    - `observed` (string, nullable, required)
    - `state` (string, one of `ok`, `alarm`, `unevaluated`, `disabled`, required)
    - `target_amount` (string, required)
  - `time_period` (object, required)
    - `end` (string, date-time, required): Exclusive end (RFC 3339).
    - `start` (string, date-time, required): Inclusive start (RFC 3339).
- `thresholds` (array of object, nullable, required)
  - `basis` (string, required)
  - `channels` (array of string, nullable, required)
  - `comparison` (string, required)
  - `enabled` (boolean, required)
  - `message` (string)
  - `name` (string, required)
  - `unit` (string, required)
  - `value` (string, required)
- `updated_at` (string, date-time, required)

### 400 — Bad Request

`application/problem+json`:

- `detail` (string): A human-readable explanation specific to this occurrence of the problem. Example: `Property foo is required but is missing.`.
- `errors` (array of object, nullable): Optional list of individual error details
  - `location` (string): Where the error occurred, e.g. 'body.items[3].tags' or 'path.thing-id'
  - `message` (string): Error message text
  - `value` (object): The value at the given location
- `instance` (string, uri): A URI reference that identifies the specific occurrence of the problem. Example: `https://example.com/error-log/abc123`.
- `status` (integer, int64): HTTP status code Example: `400`.
- `title` (string): A short, human-readable summary of the problem type. This value should not change between occurrences of the error. Example: `Bad Request`.
- `type` (string, uri): A URI reference to human-readable documentation for the error. Example: `https://example.com/errors/example`.

### 401 — Unauthorized

Same `application/problem+json` body as 400.

### 403 — Forbidden

Same `application/problem+json` body as 400.

### 404 — Not Found

Same `application/problem+json` body as 400.

### 409 — Conflict

Same `application/problem+json` body as 400.

### 422 — Unprocessable Entity

Same `application/problem+json` body as 400.

### 500 — Internal Server Error

Same `application/problem+json` body as 400.

### 503 — Service Unavailable

Same `application/problem+json` body as 400.

Full schema: https://docs.vexxhost.com/budgets/openapi.json

URL: https://docs.vexxhost.com/budgets/api/budgets/get-budget

---

# Replace a budget

`PUT https://budget.public.global.vexxhost.net/v1/budgets/{budget_id}`

The body carries the revision the budget was read at; a stale one is refused with 409.

Authentication: `X-Auth-Token: <token>` header. See https://docs.vexxhost.com/identity/authentication.md.

## Parameters

- `budget_id` (path, string, required): Budget identifier.

## Request body (application/json)

- `amount` (string, required): Budgeted amount per window, as a decimal string.
- `currency` (string): Defaults to, and must equal, the currency your costs are reported in.
- `custom_period` (object): Required when period is custom, at most 366 days.
  - `end` (string, date-time, required): Exclusive end (RFC 3339).
  - `start` (string, date-time, required): Inclusive start (RFC 3339).
- `enabled` (boolean): Defaults to true. A disabled budget is not checked and sends no alerts.
- `filter` (object)
  - `regions` (array of string, nullable): Regions to count. Empty counts every region.
  - `services` (array of string, one of `compute`, `image`, `volume`, nullable): Services to count. Empty counts every service.
  - `usage_types` (array of string, nullable): Usage types to count. Empty counts every usage type.
- `name` (string, required)
- `period` (string, one of `daily`, `monthly`, `quarterly`, `yearly`, `custom`, required): Calendar periods reset in UTC; custom is one fixed window.
- `project_id` (string): The project the budget belongs to and measures. Omitted, it is the caller's own project.
- `revision` (integer, int64, required): The revision the budget was read at. A stale revision is refused with 409.
- `thresholds` (array of object, nullable, required)
  - `basis` (string, one of `actual`, `forecasted`, required): Judge spend so far (actual) or the projection for the whole window (forecasted).
  - `channels` (array of string, nullable): Configured notification channels to alert when the threshold enters alarm. With none the threshold still tracks its state.
  - `comparison` (string, one of `greater_than`, `greater_than_or_equal`, `less_than`, `less_than_or_equal`): Defaults to greater_than_or_equal.
  - `enabled` (boolean): Defaults to true.
  - `message` (string): Text included in the alert.
  - `name` (string, required): Unique within the budget. State is tracked by name, so renaming a threshold restarts it.
  - `unit` (string, one of `percentage`, `absolute`, required): Whether value is a percentage of the budget amount or an amount in its currency.
  - `value` (string, required): Decimal string.

## Responses

### 200 — OK

`application/json`:

- `amount` (string, required)
- `created_at` (string, date-time, required)
- `created_by` (string, required)
- `currency` (string, required)
- `custom_period` (object)
  - `end` (string, date-time, required): Exclusive end (RFC 3339).
  - `start` (string, date-time, required): Inclusive start (RFC 3339).
- `enabled` (boolean, required)
- `filter` (object, required)
  - `regions` (array of string, nullable): Regions to count. Empty counts every region.
  - `services` (array of string, one of `compute`, `image`, `volume`, nullable): Services to count. Empty counts every service.
  - `usage_types` (array of string, nullable): Usage types to count. Empty counts every usage type.
- `id` (string, required)
- `name` (string, required)
- `period` (string, required)
- `project_id` (string, required)
- `revision` (integer, int64, required)
- `status` (object, required): Spend and threshold states for the current window as of its latest check, or null before the window's first check.
  - `actual_spend` (string, required)
  - `amount` (string, required): The budget amount the thresholds were judged against.
  - `as_of` (string, date-time, required): When the spend was measured.
  - `currency` (string, required)
  - `data_through` (string, date-time, required): How far the usage data is complete. Spend after this is not counted yet.
  - `estimated` (boolean, required): Always true: costs are computed from usage and a price catalogue.
  - `evaluated_at` (string, date-time, required)
  - `forecast_spend` (string, nullable, required): Linear projection for the whole window; null until enough of it has elapsed.
  - `thresholds` (array of object, nullable, required)
    - `changed_at` (string, date-time): When the threshold last entered or left alarm in this window.
    - `name` (string, required)
    - `observed` (string, nullable, required)
    - `state` (string, one of `ok`, `alarm`, `unevaluated`, `disabled`, required)
    - `target_amount` (string, required)
  - `time_period` (object, required)
    - `end` (string, date-time, required): Exclusive end (RFC 3339).
    - `start` (string, date-time, required): Inclusive start (RFC 3339).
- `thresholds` (array of object, nullable, required)
  - `basis` (string, required)
  - `channels` (array of string, nullable, required)
  - `comparison` (string, required)
  - `enabled` (boolean, required)
  - `message` (string)
  - `name` (string, required)
  - `unit` (string, required)
  - `value` (string, required)
- `updated_at` (string, date-time, required)

### 400 — Bad Request

`application/problem+json`:

- `detail` (string): A human-readable explanation specific to this occurrence of the problem. Example: `Property foo is required but is missing.`.
- `errors` (array of object, nullable): Optional list of individual error details
  - `location` (string): Where the error occurred, e.g. 'body.items[3].tags' or 'path.thing-id'
  - `message` (string): Error message text
  - `value` (object): The value at the given location
- `instance` (string, uri): A URI reference that identifies the specific occurrence of the problem. Example: `https://example.com/error-log/abc123`.
- `status` (integer, int64): HTTP status code Example: `400`.
- `title` (string): A short, human-readable summary of the problem type. This value should not change between occurrences of the error. Example: `Bad Request`.
- `type` (string, uri): A URI reference to human-readable documentation for the error. Example: `https://example.com/errors/example`.

### 401 — Unauthorized

Same `application/problem+json` body as 400.

### 403 — Forbidden

Same `application/problem+json` body as 400.

### 404 — Not Found

Same `application/problem+json` body as 400.

### 409 — Conflict

Same `application/problem+json` body as 400.

### 422 — Unprocessable Entity

Same `application/problem+json` body as 400.

### 500 — Internal Server Error

Same `application/problem+json` body as 400.

### 503 — Service Unavailable

Same `application/problem+json` body as 400.

Full schema: https://docs.vexxhost.com/budgets/openapi.json

URL: https://docs.vexxhost.com/budgets/api/budgets/update-budget

---

# Delete a budget and its history

`DELETE https://budget.public.global.vexxhost.net/v1/budgets/{budget_id}`

Authentication: `X-Auth-Token: <token>` header. See https://docs.vexxhost.com/identity/authentication.md.

## Parameters

- `budget_id` (path, string, required): Budget identifier.

## Responses

### 204 — No Content

### 400 — Bad Request

`application/problem+json`:

- `detail` (string): A human-readable explanation specific to this occurrence of the problem. Example: `Property foo is required but is missing.`.
- `errors` (array of object, nullable): Optional list of individual error details
  - `location` (string): Where the error occurred, e.g. 'body.items[3].tags' or 'path.thing-id'
  - `message` (string): Error message text
  - `value` (object): The value at the given location
- `instance` (string, uri): A URI reference that identifies the specific occurrence of the problem. Example: `https://example.com/error-log/abc123`.
- `status` (integer, int64): HTTP status code Example: `400`.
- `title` (string): A short, human-readable summary of the problem type. This value should not change between occurrences of the error. Example: `Bad Request`.
- `type` (string, uri): A URI reference to human-readable documentation for the error. Example: `https://example.com/errors/example`.

### 401 — Unauthorized

Same `application/problem+json` body as 400.

### 403 — Forbidden

Same `application/problem+json` body as 400.

### 404 — Not Found

Same `application/problem+json` body as 400.

### 409 — Conflict

Same `application/problem+json` body as 400.

### 422 — Unprocessable Entity

Same `application/problem+json` body as 400.

### 500 — Internal Server Error

Same `application/problem+json` body as 400.

### 503 — Service Unavailable

Same `application/problem+json` body as 400.

Full schema: https://docs.vexxhost.com/budgets/openapi.json

URL: https://docs.vexxhost.com/budgets/api/budgets/delete-budget

---

# List a budget's alerts and their delivery, newest first

`GET https://budget.public.global.vexxhost.net/v1/budgets/{budget_id}/notifications`

Authentication: `X-Auth-Token: <token>` header. See https://docs.vexxhost.com/identity/authentication.md.

## Parameters

- `budget_id` (path, string, required): Budget identifier.
- `limit` (query, integer)

## Responses

### 200 — OK

`application/json`:

- `notifications` (array of object, nullable, required)
  - `attempts` (integer, int64, required)
  - `channel_id` (string, required)
  - `created_at` (string, date-time, required)
  - `id` (integer, int64, required)
  - `last_error` (string)
  - `observed` (string, required)
  - `sent_at` (string, date-time)
  - `status` (string, one of `pending`, `sent`, `failed`, required)
  - `target_amount` (string, required)
  - `threshold` (string, required)
  - `time_period` (object, required)
    - `end` (string, date-time, required): Exclusive end (RFC 3339).
    - `start` (string, date-time, required): Inclusive start (RFC 3339).

### 400 — Bad Request

`application/problem+json`:

- `detail` (string): A human-readable explanation specific to this occurrence of the problem. Example: `Property foo is required but is missing.`.
- `errors` (array of object, nullable): Optional list of individual error details
  - `location` (string): Where the error occurred, e.g. 'body.items[3].tags' or 'path.thing-id'
  - `message` (string): Error message text
  - `value` (object): The value at the given location
- `instance` (string, uri): A URI reference that identifies the specific occurrence of the problem. Example: `https://example.com/error-log/abc123`.
- `status` (integer, int64): HTTP status code Example: `400`.
- `title` (string): A short, human-readable summary of the problem type. This value should not change between occurrences of the error. Example: `Bad Request`.
- `type` (string, uri): A URI reference to human-readable documentation for the error. Example: `https://example.com/errors/example`.

### 401 — Unauthorized

Same `application/problem+json` body as 400.

### 403 — Forbidden

Same `application/problem+json` body as 400.

### 404 — Not Found

Same `application/problem+json` body as 400.

### 409 — Conflict

Same `application/problem+json` body as 400.

### 422 — Unprocessable Entity

Same `application/problem+json` body as 400.

### 500 — Internal Server Error

Same `application/problem+json` body as 400.

### 503 — Service Unavailable

Same `application/problem+json` body as 400.

Full schema: https://docs.vexxhost.com/budgets/openapi.json

URL: https://docs.vexxhost.com/budgets/api/budgets/list-budget-notifications

---

# List a budget's past and current windows, newest first

`GET https://budget.public.global.vexxhost.net/v1/budgets/{budget_id}/windows`

Authentication: `X-Auth-Token: <token>` header. See https://docs.vexxhost.com/identity/authentication.md.

## Parameters

- `budget_id` (path, string, required): Budget identifier.
- `limit` (query, integer)

## Responses

### 200 — OK

`application/json`:

- `windows` (array of object, nullable, required)
  - `actual_spend` (string, required)
  - `amount` (string, required): The budget amount the thresholds were judged against.
  - `as_of` (string, date-time, required): When the spend was measured.
  - `currency` (string, required)
  - `data_through` (string, date-time, required): How far the usage data is complete. Spend after this is not counted yet.
  - `estimated` (boolean, required): Always true: costs are computed from usage and a price catalogue.
  - `evaluated_at` (string, date-time, required)
  - `forecast_spend` (string, nullable, required): Linear projection for the whole window; null until enough of it has elapsed.
  - `thresholds` (array of object, nullable, required)
    - `changed_at` (string, date-time): When the threshold last entered or left alarm in this window.
    - `name` (string, required)
    - `observed` (string, nullable, required)
    - `state` (string, one of `ok`, `alarm`, `unevaluated`, `disabled`, required)
    - `target_amount` (string, required)
  - `time_period` (object, required)
    - `end` (string, date-time, required): Exclusive end (RFC 3339).
    - `start` (string, date-time, required): Inclusive start (RFC 3339).

### 400 — Bad Request

`application/problem+json`:

- `detail` (string): A human-readable explanation specific to this occurrence of the problem. Example: `Property foo is required but is missing.`.
- `errors` (array of object, nullable): Optional list of individual error details
  - `location` (string): Where the error occurred, e.g. 'body.items[3].tags' or 'path.thing-id'
  - `message` (string): Error message text
  - `value` (object): The value at the given location
- `instance` (string, uri): A URI reference that identifies the specific occurrence of the problem. Example: `https://example.com/error-log/abc123`.
- `status` (integer, int64): HTTP status code Example: `400`.
- `title` (string): A short, human-readable summary of the problem type. This value should not change between occurrences of the error. Example: `Bad Request`.
- `type` (string, uri): A URI reference to human-readable documentation for the error. Example: `https://example.com/errors/example`.

### 401 — Unauthorized

Same `application/problem+json` body as 400.

### 403 — Forbidden

Same `application/problem+json` body as 400.

### 404 — Not Found

Same `application/problem+json` body as 400.

### 409 — Conflict

Same `application/problem+json` body as 400.

### 422 — Unprocessable Entity

Same `application/problem+json` body as 400.

### 500 — Internal Server Error

Same `application/problem+json` body as 400.

### 503 — Service Unavailable

Same `application/problem+json` body as 400.

Full schema: https://docs.vexxhost.com/budgets/openapi.json

URL: https://docs.vexxhost.com/budgets/api/budgets/list-budget-windows

---

# List notification channels

`GET https://budget.public.global.vexxhost.net/v1/notification_channels`

Authentication: `X-Auth-Token: <token>` header. See https://docs.vexxhost.com/identity/authentication.md.

## Parameters

- `project_id` (query, string): The project whose channels to list. Defaults to your token's project; naming another project is refused with 403.

## Responses

### 200 — OK

`application/json`:

- `channels` (array of object, nullable, required)
  - `address` (string)
  - `created_at` (string, date-time, required)
  - `created_by` (string, required)
  - `id` (string, required)
  - `name` (string, required)
  - `project_id` (string, required)
  - `secret` (string): A webhook's signing secret. It is returned only when the channel is created and when the secret is rotated.
  - `status` (string, one of `pending_confirmation`, `confirmed`, required)
  - `type` (string, required)
  - `url` (string)

### 400 — Bad Request

`application/problem+json`:

- `detail` (string): A human-readable explanation specific to this occurrence of the problem. Example: `Property foo is required but is missing.`.
- `errors` (array of object, nullable): Optional list of individual error details
  - `location` (string): Where the error occurred, e.g. 'body.items[3].tags' or 'path.thing-id'
  - `message` (string): Error message text
  - `value` (object): The value at the given location
- `instance` (string, uri): A URI reference that identifies the specific occurrence of the problem. Example: `https://example.com/error-log/abc123`.
- `status` (integer, int64): HTTP status code Example: `400`.
- `title` (string): A short, human-readable summary of the problem type. This value should not change between occurrences of the error. Example: `Bad Request`.
- `type` (string, uri): A URI reference to human-readable documentation for the error. Example: `https://example.com/errors/example`.

### 401 — Unauthorized

Same `application/problem+json` body as 400.

### 403 — Forbidden

Same `application/problem+json` body as 400.

### 404 — Not Found

Same `application/problem+json` body as 400.

### 409 — Conflict

Same `application/problem+json` body as 400.

### 422 — Unprocessable Entity

Same `application/problem+json` body as 400.

### 500 — Internal Server Error

Same `application/problem+json` body as 400.

### 503 — Service Unavailable

Same `application/problem+json` body as 400.

Full schema: https://docs.vexxhost.com/budgets/openapi.json

URL: https://docs.vexxhost.com/budgets/api/notification-channels/list-notification-channels

---

# Create a notification channel

`POST https://budget.public.global.vexxhost.net/v1/notification_channels`

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.

Authentication: `X-Auth-Token: <token>` header. See https://docs.vexxhost.com/identity/authentication.md.

## Request body (application/json)

- `address` (string): For an email channel: the address. It receives nothing until its owner follows the confirmation link sent to it.
- `name` (string, required)
- `project_id` (string): The owning project. Omitted, it is the caller's own project.
- `type` (string, one of `email`, `webhook`, required)
- `url` (string): For a webhook channel: an https URL on a public address. Deliveries are signed; see X-Budget-Signature.

## Responses

### 201 — Created

`application/json`:

- `address` (string)
- `created_at` (string, date-time, required)
- `created_by` (string, required)
- `id` (string, required)
- `name` (string, required)
- `project_id` (string, required)
- `secret` (string): A webhook's signing secret. It is returned only when the channel is created and when the secret is rotated.
- `status` (string, one of `pending_confirmation`, `confirmed`, required)
- `type` (string, required)
- `url` (string)

### 400 — Bad Request

`application/problem+json`:

- `detail` (string): A human-readable explanation specific to this occurrence of the problem. Example: `Property foo is required but is missing.`.
- `errors` (array of object, nullable): Optional list of individual error details
  - `location` (string): Where the error occurred, e.g. 'body.items[3].tags' or 'path.thing-id'
  - `message` (string): Error message text
  - `value` (object): The value at the given location
- `instance` (string, uri): A URI reference that identifies the specific occurrence of the problem. Example: `https://example.com/error-log/abc123`.
- `status` (integer, int64): HTTP status code Example: `400`.
- `title` (string): A short, human-readable summary of the problem type. This value should not change between occurrences of the error. Example: `Bad Request`.
- `type` (string, uri): A URI reference to human-readable documentation for the error. Example: `https://example.com/errors/example`.

### 401 — Unauthorized

Same `application/problem+json` body as 400.

### 403 — Forbidden

Same `application/problem+json` body as 400.

### 404 — Not Found

Same `application/problem+json` body as 400.

### 409 — Conflict

Same `application/problem+json` body as 400.

### 422 — Unprocessable Entity

Same `application/problem+json` body as 400.

### 500 — Internal Server Error

Same `application/problem+json` body as 400.

### 503 — Service Unavailable

Same `application/problem+json` body as 400.

Full schema: https://docs.vexxhost.com/budgets/openapi.json

URL: https://docs.vexxhost.com/budgets/api/notification-channels/create-notification-channel

---

# Get a notification channel

`GET https://budget.public.global.vexxhost.net/v1/notification_channels/{channel_id}`

Authentication: `X-Auth-Token: <token>` header. See https://docs.vexxhost.com/identity/authentication.md.

## Parameters

- `channel_id` (path, string, required): Notification channel identifier.

## Responses

### 200 — OK

`application/json`:

- `address` (string)
- `created_at` (string, date-time, required)
- `created_by` (string, required)
- `id` (string, required)
- `name` (string, required)
- `project_id` (string, required)
- `secret` (string): A webhook's signing secret. It is returned only when the channel is created and when the secret is rotated.
- `status` (string, one of `pending_confirmation`, `confirmed`, required)
- `type` (string, required)
- `url` (string)

### 400 — Bad Request

`application/problem+json`:

- `detail` (string): A human-readable explanation specific to this occurrence of the problem. Example: `Property foo is required but is missing.`.
- `errors` (array of object, nullable): Optional list of individual error details
  - `location` (string): Where the error occurred, e.g. 'body.items[3].tags' or 'path.thing-id'
  - `message` (string): Error message text
  - `value` (object): The value at the given location
- `instance` (string, uri): A URI reference that identifies the specific occurrence of the problem. Example: `https://example.com/error-log/abc123`.
- `status` (integer, int64): HTTP status code Example: `400`.
- `title` (string): A short, human-readable summary of the problem type. This value should not change between occurrences of the error. Example: `Bad Request`.
- `type` (string, uri): A URI reference to human-readable documentation for the error. Example: `https://example.com/errors/example`.

### 401 — Unauthorized

Same `application/problem+json` body as 400.

### 403 — Forbidden

Same `application/problem+json` body as 400.

### 404 — Not Found

Same `application/problem+json` body as 400.

### 409 — Conflict

Same `application/problem+json` body as 400.

### 422 — Unprocessable Entity

Same `application/problem+json` body as 400.

### 500 — Internal Server Error

Same `application/problem+json` body as 400.

### 503 — Service Unavailable

Same `application/problem+json` body as 400.

Full schema: https://docs.vexxhost.com/budgets/openapi.json

URL: https://docs.vexxhost.com/budgets/api/notification-channels/get-notification-channel

---

# Delete a notification channel

`DELETE https://budget.public.global.vexxhost.net/v1/notification_channels/{channel_id}`

Refused with 409 while a budget's threshold names it.

Authentication: `X-Auth-Token: <token>` header. See https://docs.vexxhost.com/identity/authentication.md.

## Parameters

- `channel_id` (path, string, required): Notification channel identifier.

## Responses

### 204 — No Content

### 400 — Bad Request

`application/problem+json`:

- `detail` (string): A human-readable explanation specific to this occurrence of the problem. Example: `Property foo is required but is missing.`.
- `errors` (array of object, nullable): Optional list of individual error details
  - `location` (string): Where the error occurred, e.g. 'body.items[3].tags' or 'path.thing-id'
  - `message` (string): Error message text
  - `value` (object): The value at the given location
- `instance` (string, uri): A URI reference that identifies the specific occurrence of the problem. Example: `https://example.com/error-log/abc123`.
- `status` (integer, int64): HTTP status code Example: `400`.
- `title` (string): A short, human-readable summary of the problem type. This value should not change between occurrences of the error. Example: `Bad Request`.
- `type` (string, uri): A URI reference to human-readable documentation for the error. Example: `https://example.com/errors/example`.

### 401 — Unauthorized

Same `application/problem+json` body as 400.

### 403 — Forbidden

Same `application/problem+json` body as 400.

### 404 — Not Found

Same `application/problem+json` body as 400.

### 409 — Conflict

Same `application/problem+json` body as 400.

### 422 — Unprocessable Entity

Same `application/problem+json` body as 400.

### 500 — Internal Server Error

Same `application/problem+json` body as 400.

### 503 — Service Unavailable

Same `application/problem+json` body as 400.

Full schema: https://docs.vexxhost.com/budgets/openapi.json

URL: https://docs.vexxhost.com/budgets/api/notification-channels/delete-notification-channel

---

# Send an email channel's confirmation link again

`POST https://budget.public.global.vexxhost.net/v1/notification_channels/{channel_id}/resend_confirmation`

At most one is sent every ten minutes.

Authentication: `X-Auth-Token: <token>` header. See https://docs.vexxhost.com/identity/authentication.md.

## Parameters

- `channel_id` (path, string, required): Notification channel identifier.

## Responses

### 202 — Accepted

### 400 — Bad Request

`application/problem+json`:

- `detail` (string): A human-readable explanation specific to this occurrence of the problem. Example: `Property foo is required but is missing.`.
- `errors` (array of object, nullable): Optional list of individual error details
  - `location` (string): Where the error occurred, e.g. 'body.items[3].tags' or 'path.thing-id'
  - `message` (string): Error message text
  - `value` (object): The value at the given location
- `instance` (string, uri): A URI reference that identifies the specific occurrence of the problem. Example: `https://example.com/error-log/abc123`.
- `status` (integer, int64): HTTP status code Example: `400`.
- `title` (string): A short, human-readable summary of the problem type. This value should not change between occurrences of the error. Example: `Bad Request`.
- `type` (string, uri): A URI reference to human-readable documentation for the error. Example: `https://example.com/errors/example`.

### 401 — Unauthorized

Same `application/problem+json` body as 400.

### 403 — Forbidden

Same `application/problem+json` body as 400.

### 404 — Not Found

Same `application/problem+json` body as 400.

### 409 — Conflict

Same `application/problem+json` body as 400.

### 422 — Unprocessable Entity

Same `application/problem+json` body as 400.

### 500 — Internal Server Error

Same `application/problem+json` body as 400.

### 503 — Service Unavailable

Same `application/problem+json` body as 400.

Full schema: https://docs.vexxhost.com/budgets/openapi.json

URL: https://docs.vexxhost.com/budgets/api/notification-channels/resend-channel-confirmation

---

# Replace a webhook channel's signing secret

`POST https://budget.public.global.vexxhost.net/v1/notification_channels/{channel_id}/rotate_secret`

Authentication: `X-Auth-Token: <token>` header. See https://docs.vexxhost.com/identity/authentication.md.

## Parameters

- `channel_id` (path, string, required): Notification channel identifier.

## Responses

### 200 — OK

`application/json`:

- `address` (string)
- `created_at` (string, date-time, required)
- `created_by` (string, required)
- `id` (string, required)
- `name` (string, required)
- `project_id` (string, required)
- `secret` (string): A webhook's signing secret. It is returned only when the channel is created and when the secret is rotated.
- `status` (string, one of `pending_confirmation`, `confirmed`, required)
- `type` (string, required)
- `url` (string)

### 400 — Bad Request

`application/problem+json`:

- `detail` (string): A human-readable explanation specific to this occurrence of the problem. Example: `Property foo is required but is missing.`.
- `errors` (array of object, nullable): Optional list of individual error details
  - `location` (string): Where the error occurred, e.g. 'body.items[3].tags' or 'path.thing-id'
  - `message` (string): Error message text
  - `value` (object): The value at the given location
- `instance` (string, uri): A URI reference that identifies the specific occurrence of the problem. Example: `https://example.com/error-log/abc123`.
- `status` (integer, int64): HTTP status code Example: `400`.
- `title` (string): A short, human-readable summary of the problem type. This value should not change between occurrences of the error. Example: `Bad Request`.
- `type` (string, uri): A URI reference to human-readable documentation for the error. Example: `https://example.com/errors/example`.

### 401 — Unauthorized

Same `application/problem+json` body as 400.

### 403 — Forbidden

Same `application/problem+json` body as 400.

### 404 — Not Found

Same `application/problem+json` body as 400.

### 409 — Conflict

Same `application/problem+json` body as 400.

### 422 — Unprocessable Entity

Same `application/problem+json` body as 400.

### 500 — Internal Server Error

Same `application/problem+json` body as 400.

### 503 — Service Unavailable

Same `application/problem+json` body as 400.

Full schema: https://docs.vexxhost.com/budgets/openapi.json

URL: https://docs.vexxhost.com/budgets/api/notification-channels/rotate-channel-secret

---

# Budgets CLI reference

> Look up every Budgets command in the openstack CLI and its options.

The `vexxhost-budgetclient` package adds the Budgets commands to the `openstack` CLI. This reference covers version 0.1.1. Install the package with pip:

```sh
pip install vexxhost-budgetclient
```

To sign in and choose an output format, see [Set up the CLI](/developer/cli).

| Command                                                                                                          | Description                                                        |
| ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| [`openstack budget channel confirmation resend`](/budgets/cli/budget-channel/budget-channel-confirmation-resend) | Send an email channel's confirmation link again (once per 10 min). |
| [`openstack budget channel create`](/budgets/cli/budget-channel/budget-channel-create)                           | Create a webhook or email notification channel.                    |
| [`openstack budget channel delete`](/budgets/cli/budget-channel/budget-channel-delete)                           | Delete notification channels.                                      |
| [`openstack budget channel list`](/budgets/cli/budget-channel/budget-channel-list)                               | List notification channels.                                        |
| [`openstack budget channel secret rotate`](/budgets/cli/budget-channel/budget-channel-secret-rotate)             | Replace a webhook channel's signing secret and show the new one.   |
| [`openstack budget channel show`](/budgets/cli/budget-channel/budget-channel-show)                               | Show a notification channel.                                       |
| [`openstack budget create`](/budgets/cli/budget/budget-create)                                                   | Create a budget with alert thresholds.                             |
| [`openstack budget delete`](/budgets/cli/budget/budget-delete)                                                   | Delete budgets.                                                    |
| [`openstack budget list`](/budgets/cli/budget/budget-list)                                                       | List budgets with their spend in the current window.               |
| [`openstack budget notification list`](/budgets/cli/budget-notification/budget-notification-list)                | List the alerts a budget's thresholds raised, newest first.        |
| [`openstack budget set`](/budgets/cli/budget/budget-set)                                                         | Change a budget.                                                   |
| [`openstack budget show`](/budgets/cli/budget/budget-show)                                                       | Show a budget and the status of its current window.                |
| [`openstack budget window list`](/budgets/cli/budget-window/budget-window-list)                                  | List a budget's past and current windows, newest first.            |

URL: https://docs.vexxhost.com/budgets/cli

---

# openstack budget channel confirmation resend

> Send an email channel's confirmation link again (once per 10 min).

## Usage

```sh
openstack budget channel confirmation resend \
    <channel>
```

## Arguments

* `<channel>`

  Name or ID.

URL: https://docs.vexxhost.com/budgets/cli/budget-channel/budget-channel-confirmation-resend

---

# openstack budget channel create

> Create a webhook or email notification channel.

A webhook's signing secret is shown once, here; rotate it to get a new
one. An email address receives nothing until its owner follows the
confirmation link sent to it.

## Usage

```sh
openstack budget channel create \
    --type {email,webhook} \
    [--url <url>] \
    [--address <address>] \
    [--project <project>] \
    [--project-domain <project-domain>] \
    <name>
```

## Arguments

* `<name>`

* `--type <type>` *(required)*

  One of: `email`, `webhook`.

* `--url <url>`

  Webhook: an https URL on a public address.

* `--address <address>`

  Email: the address to notify.

* `--project <project>`

  Owning project (name or ID; defaults to your own).

* `--project-domain <project-domain>`

  Domain the project belongs to (name or ID). This can be used in case collisions between project names exist.

## Output

This command also takes the common output options, such as `-f json` and `-c <column>`. See [Set up the CLI](/developer/cli).

URL: https://docs.vexxhost.com/budgets/cli/budget-channel/budget-channel-create

---

# openstack budget channel delete

> Delete notification channels.

A channel a budget's threshold still names cannot be deleted.

## Usage

```sh
openstack budget channel delete \
    <channel> ...
```

## Arguments

* `<channel>` *(repeatable)*

  Name or ID.

URL: https://docs.vexxhost.com/budgets/cli/budget-channel/budget-channel-delete

---

# openstack budget channel list

> List notification channels.

## Usage

```sh
openstack budget channel list \
    [--project <project>] \
    [--project-domain <project-domain>]
```

## Arguments

* `--project <project>`

  Administrators only: list one project's channels.

* `--project-domain <project-domain>`

  Domain the project belongs to (name or ID). This can be used in case collisions between project names exist.

## Output

This command also takes the common output options, such as `-f json` and `-c <column>`. See [Set up the CLI](/developer/cli).

URL: https://docs.vexxhost.com/budgets/cli/budget-channel/budget-channel-list

---

# openstack budget channel secret rotate

> Replace a webhook channel's signing secret and show the new one.

## Usage

```sh
openstack budget channel secret rotate \
    <channel>
```

## Arguments

* `<channel>`

  Name or ID.

## Output

This command also takes the common output options, such as `-f json` and `-c <column>`. See [Set up the CLI](/developer/cli).

URL: https://docs.vexxhost.com/budgets/cli/budget-channel/budget-channel-secret-rotate

---

# openstack budget channel show

> Show a notification channel.

## Usage

```sh
openstack budget channel show \
    <channel>
```

## Arguments

* `<channel>`

  Name or ID.

## Output

This command also takes the common output options, such as `-f json` and `-c <column>`. See [Set up the CLI](/developer/cli).

URL: https://docs.vexxhost.com/budgets/cli/budget-channel/budget-channel-show

---

# openstack budget create

> Create a budget with alert thresholds.

## Usage

```sh
openstack budget create \
    --amount <amount> \
    --period {daily,monthly,quarterly,yearly,custom} \
    [--start <time>] \
    [--end <time>] \
    [--currency <currency>] \
    [--enable] \
    [--disable] \
    [--service {compute,volume,image}] ... \
    [--region <region>] ... \
    [--usage-type <usage-type>] ... \
    --threshold name=<name>,value=<value>[,basis=..][,unit=..][,...] ... \
    [--project <project>] \
    [--project-domain <project-domain>] \
    <name>
```

## Arguments

* `<name>`

* `--amount <amount>` *(required)*

  Amount per window.

* `--period <period>` *(required)*

  Calendar periods reset in UTC; custom is one fixed window set by --start and --end.

  One of: `daily`, `monthly`, `quarterly`, `yearly`, `custom`.

* `--start <time>`

  Start of a custom period (inclusive).

* `--end <time>`

  End of a custom period (exclusive).

* `--currency <currency>`

  Defaults to, and must be, the currency your costs are reported in.

* `--enable`

* `--disable`

  A disabled budget is not checked and sends no alerts.

* `--service <service>` *(repeatable)*

  Service to count (repeatable; default every one).

  One of: `compute`, `volume`, `image`.

* `--region <region>` *(repeatable)*

  Region to count (repeatable; default every one).

* `--usage-type <usage-type>` *(repeatable)*

  Usage type to count (repeatable; default every one).

* `--threshold name=<name>,value=<value>[,basis=..][,unit=..][,...]` *(required, repeatable)*

  An alert rule (repeat for more, up to 10). Keys: name, value, basis (actual|forecasted, default actual), unit (percentage|absolute, default percentage), comparison (greater_than|greater_than_or_equal|less_than|less_than_or_equal, default greater_than_or_equal), channel (a notification channel name or ID; repeatable), message, enabled (true|false).

* `--project <project>`

  Owning project (name or ID; defaults to your own).

* `--project-domain <project-domain>`

  Domain the project belongs to (name or ID). This can be used in case collisions between project names exist.

## Output

This command also takes the common output options, such as `-f json` and `-c <column>`. See [Set up the CLI](/developer/cli).

URL: https://docs.vexxhost.com/budgets/cli/budget/budget-create

---

# openstack budget delete

> Delete budgets.

## Usage

```sh
openstack budget delete \
    <budget> ...
```

## Arguments

* `<budget>` *(repeatable)*

  Name or ID.

URL: https://docs.vexxhost.com/budgets/cli/budget/budget-delete

---

# openstack budget list

> List budgets with their spend in the current window.

## Usage

```sh
openstack budget list \
    [--project <project>] \
    [--project-domain <project-domain>]
```

## Arguments

* `--project <project>`

  Administrators only: list one project's budgets.

* `--project-domain <project-domain>`

  Domain the project belongs to (name or ID). This can be used in case collisions between project names exist.

## Output

This command also takes the common output options, such as `-f json` and `-c <column>`. See [Set up the CLI](/developer/cli).

URL: https://docs.vexxhost.com/budgets/cli/budget/budget-list

---

# openstack budget set

> Change a budget.

The budget is read, changed and written back with the revision it was
read at, so a concurrent change is refused instead of overwritten.

## Usage

```sh
openstack budget set \
    [--name <name>] \
    [--amount <amount>] \
    [--period {daily,monthly,quarterly,yearly,custom}] \
    [--start <time>] \
    [--end <time>] \
    [--currency <currency>] \
    [--enable] \
    [--disable] \
    [--service {compute,volume,image}] ... \
    [--no-service] \
    [--region <region>] ... \
    [--no-region] \
    [--usage-type <usage-type>] ... \
    [--no-usage-type] \
    [--threshold name=<name>,value=<value>[,basis=..][,unit=..][,...]] ... \
    <budget>
```

## Arguments

* `<budget>`

  Name or ID.

* `--name <name>`

  New name.

* `--amount <amount>`

  Amount per window.

* `--period <period>`

  Calendar periods reset in UTC; custom is one fixed window set by --start and --end.

  One of: `daily`, `monthly`, `quarterly`, `yearly`, `custom`.

* `--start <time>`

  Start of a custom period (inclusive).

* `--end <time>`

  End of a custom period (exclusive).

* `--currency <currency>`

  Defaults to, and must be, the currency your costs are reported in.

* `--enable`

* `--disable`

  A disabled budget is not checked and sends no alerts.

* `--service <service>` *(repeatable)*

  Service to count (repeatable; default every one); replaces the current list.

  One of: `compute`, `volume`, `image`.

* `--no-service`

  Count every service again.

* `--region <region>` *(repeatable)*

  Region to count (repeatable; default every one); replaces the current list.

* `--no-region`

  Count every region again.

* `--usage-type <usage-type>` *(repeatable)*

  Usage type to count (repeatable; default every one); replaces the current list.

* `--no-usage-type`

  Count every usage type again.

* `--threshold name=<name>,value=<value>[,basis=..][,unit=..][,...]` *(repeatable)*

  An alert rule (repeat for more, up to 10). Keys: name, value, basis (actual|forecasted, default actual), unit (percentage|absolute, default percentage), comparison (greater_than|greater_than_or_equal|less_than|less_than_or_equal, default greater_than_or_equal), channel (a notification channel name or ID; repeatable), message, enabled (true|false). Replaces all existing thresholds.

## Output

This command also takes the common output options, such as `-f json` and `-c <column>`. See [Set up the CLI](/developer/cli).

URL: https://docs.vexxhost.com/budgets/cli/budget/budget-set

---

# openstack budget show

> Show a budget and the status of its current window.

## Usage

```sh
openstack budget show \
    <budget>
```

## Arguments

* `<budget>`

  Name or ID.

## Output

This command also takes the common output options, such as `-f json` and `-c <column>`. See [Set up the CLI](/developer/cli).

URL: https://docs.vexxhost.com/budgets/cli/budget/budget-show

---

# openstack budget notification list

> List the alerts a budget's thresholds raised, newest first.

## Usage

```sh
openstack budget notification list \
    [--limit <n>] \
    <budget>
```

## Arguments

* `<budget>`

  Name or ID.

* `--limit <n>`

  At most this many.

## Output

This command also takes the common output options, such as `-f json` and `-c <column>`. See [Set up the CLI](/developer/cli).

URL: https://docs.vexxhost.com/budgets/cli/budget-notification/budget-notification-list

---

# openstack budget window list

> List a budget's past and current windows, newest first.

## Usage

```sh
openstack budget window list \
    [--limit <n>] \
    <budget>
```

## Arguments

* `<budget>`

  Name or ID.

* `--limit <n>`

  At most this many.

## Output

This command also takes the common output options, such as `-f json` and `-c <column>`. See [Set up the CLI](/developer/cli).

URL: https://docs.vexxhost.com/budgets/cli/budget-window/budget-window-list

---

# Limits

> The limits that apply to Budgets budgets, thresholds, notification channels and alert delivery.

These limits apply to every project. Contact support if a project needs more budgets or
notification channels.

## Budgets and thresholds

| Limit                             | Value                 |
| --------------------------------- | --------------------- |
| Budgets per project               | 20                    |
| Thresholds per budget             | 1 to 10               |
| Length of a `custom` period       | Up to 366 days        |
| Forecasted thresholds start after | One day of the window |
| How often budgets are checked     | About once an hour    |
| Windows and alerts kept           | 400 days              |

## Notification channels and delivery

| Limit                                              | Value                                                        |
| -------------------------------------------------- | ------------------------------------------------------------ |
| Notification channels per project                  | 10                                                           |
| An email channel's confirmation link expires after | 72 hours                                                     |
| Resending a confirmation                           | At most once every 10 minutes                                |
| Webhook response time                              | 10 seconds                                                   |
| Delivery retries                                   | For about four hours, then Budgets marks the alert as failed |

## API

| Limit                                          | Value                                 |
| ---------------------------------------------- | ------------------------------------- |
| Windows or alerts returned by one list request | 50 by default, up to 500 with `limit` |

URL: https://docs.vexxhost.com/budgets/platform/limits

---

# Developer guide

> Set up the OpenStack CLI and the OpenStack SDK for Python, and make API requests to any service.

Every service works with the same tools. You sign in through [Identity](/identity), use
the standard `openstack` command line client and the OpenStack SDK, and send API requests with
the same token. Set the tools up once here. Each product's documentation then covers only what is
specific to it.

<Cards>
  <Card title="Set up the CLI" href="/developer/cli" description="Install the OpenStack CLI and sign it in." />

  <Card title="Use OpenStack from Python" href="/developer/python" description="Connect with the OpenStack SDK and use its session with other clients." />

  <Card title="Make API requests" href="/developer/api" description="Authenticate, find endpoints, handle errors and retry." />

  <Card title="Agent setup" href="/developer/ai" description="Point AI assistants and coding agents at this documentation in Markdown." />
</Cards>

URL: https://docs.vexxhost.com/developer

---

# Set up the CLI

> Install the OpenStack CLI, sign it in with an application credential, choose a region, and format its output.

This guide installs the standard `openstack` command line client and signs it in to your project.
The same client works with every service.

## Before you begin

You need Python 3.10 or later, and an [application credential](/identity/application-credentials)
for the project you want to work in.

## 1. Install the client

```sh
pip install python-openstackclient
```

If you prefer to keep command line tools isolated, install it with `pipx` instead:

```sh
pipx install python-openstackclient
```

Some services add their commands through a separate package. Their CLI reference names the package
to install, and `pipx inject python-openstackclient <package>` adds it to a `pipx` installation.

## 2. Sign in

The client reads credentials from `clouds.yaml`. Add an entry for your application credential and
the region you work in:

```yaml title="~/.config/openstack/clouds.yaml"
clouds:
  vexxhost:
    auth_type: v3applicationcredential
    auth:
      auth_url: https://auth.vexxhost.net/v3
      application_credential_id: 3c2b8e1f4a6d4c0e9b7a5f3d1e2c4b6a
      application_credential_secret: Jq7mX2vL9pR4tW8yZ1bC5nF3hK6dS0gAuE2iO7qT4wY9rU1oP3aL6sD8fG5hJ2kZ0xC7vB4nM1qW8eR3tY6u
    region_name: <region>
```

`openstack region list` shows the regions. Then choose the entry for your shell:

```sh
export OS_CLOUD=vexxhost
```

The client finds each service in the [service catalog](/identity/service-catalog), so it needs no
other settings.

## 3. Choose an output format

Commands print tables by default. Commands that print a resource or a list also take the standard
output options, which are useful in scripts:

| Option                           | Effect                                            |
| -------------------------------- | ------------------------------------------------- |
| `-f json`, `-f yaml`             | Machine-readable output.                          |
| `-f value`                       | Values only, one row per line.                    |
| `-f shell`                       | `name="value"` lines to `eval`, for one resource. |
| `-f csv`                         | Comma-separated rows, for lists.                  |
| `-c <column>`                    | Only this column. Repeat for more columns.        |
| `--noindent`                     | Compact JSON.                                     |
| `--max-width <n>`, `--fit-width` | Wrap wide tables.                                 |
| `--sort-column <column>`         | Sort a list by a column.                          |

For example, this lists your projects as JSON:

```sh
openstack project list --my-projects -f json
```

```json
[
  {
    "ID": "a6f1c2d93b8e4f07b5d4e2c1a9f8e7d6",
    "Name": "acme-production"
  }
]
```

## Verify

```sh
openstack token issue -c project_id
```

The output shows the project your credential belongs to. Every command acts on that project.

## Next steps

* Look up any command's options with `openstack help <command>`.
* [Use OpenStack from Python](/developer/python) with the same `clouds.yaml` entry.

URL: https://docs.vexxhost.com/developer/cli

---

# Use OpenStack from Python

> Connect with the OpenStack SDK for Python, reuse its session with other client libraries, and handle API errors.

This guide connects from Python with the OpenStack SDK, the standard Python library for
OpenStack services.

## Before you begin

You need Python 3.10 or later, and either a `clouds.yaml` entry from [Set up the CLI](/developer/cli)
or an [application credential](/identity/application-credentials).

## 1. Install the SDK

```sh
pip install openstacksdk
```

## 2. Connect

Connect with the same `clouds.yaml` entry the CLI uses:

```python
import openstack

conn = openstack.connect(cloud="vexxhost")
```

Or pass an application credential directly:

```python
import openstack

conn = openstack.connect(
    auth_type="v3applicationcredential",
    auth_url="https://auth.vexxhost.net/v3",
    application_credential_id="3c2b8e1f4a6d4c0e9b7a5f3d1e2c4b6a",
    application_credential_secret="Jq7mX2vL9pR4tW8yZ1bC5nF3hK6dS0gAuE2iO7qT4wY9rU1oP3aL6sD8fG5hJ2kZ0xC7vB4nM1qW8eR3tY6u",
    region_name="<region>",
)
```

The connection gets tokens and renews them when they expire, and it finds each service in the
[service catalog](/identity/service-catalog).

## 3. Make a call

```python
for project in conn.identity.user_projects(conn.current_user_id):
    print(project.id, project.name)
```

The SDK has a proxy for each OpenStack service, such as `conn.compute`, `conn.network` and
`conn.block_storage`.

## 4. Use the session with other clients

Some services have their own client library. Those clients take a keystoneauth session, and the
connection has one, already signed in:

```python
client = Client("1", session=conn.session)
```

Each product's documentation names its client library.

## 5. Handle errors

A failed request raises an exception from `openstack.exceptions`, such as `NotFoundException`,
`ForbiddenException` or `ConflictException`. Each carries the details of the response:

| Attribute     | Contents                                |
| ------------- | --------------------------------------- |
| `status_code` | The HTTP status.                        |
| `details`     | The error message the service returned. |
| `request_id`  | The request ID, for support.            |

```python
from openstack import exceptions

try:
    conn.identity.get_project("7d3e9b1c5a2f4e8d9c6b0a1f3e5d7c9b")
except exceptions.HttpException as error:
    print(error.status_code, error.request_id, error.details)
    raise
```

## Next steps

* Read the [OpenStack SDK documentation](https://docs.openstack.org/openstacksdk/latest/user/) for
  every service's calls.
* [Make API requests](/developer/api) to services directly.

URL: https://docs.vexxhost.com/developer/python

---

# Make API requests

> Authenticate, find endpoints, read errors and retry when you call the APIs directly over HTTPS.

Every service has an HTTP API. The `openstack` CLI and the OpenStack SDK call
these APIs for you. This page is for calling them directly.

## Authenticate and find the endpoint

Every request needs a token from Identity in the `X-Auth-Token` header. See
[Get a token](/identity/get-a-token). Find the service's endpoint for your region in the
[service catalog](/identity/service-catalog). Each service's API reference lists its paths, which
start with a version, such as `/v3/` or `/v1/`.

This request asks Identity which projects you can access:

```sh
curl https://auth.vexxhost.net/v3/auth/projects \
  --header "X-Auth-Token: $TOKEN"
```

## Send and receive JSON

Request and response bodies are JSON. Send `Content-Type: application/json` with a body. Some
services let you choose a newer version of their API for each request with the
`OpenStack-API-Version` header. Their API reference says when.

## Read errors

The status code shows what went wrong and whether to retry. The body adds detail in a format
that varies by service. Each API reference describes its own.

| Status                     | Meaning                                                                               | Retry                             |
| -------------------------- | ------------------------------------------------------------------------------------- | --------------------------------- |
| `400 Bad Request`          | The request cannot be done as written.                                                | No. Fix the request.              |
| `401 Unauthorized`         | The token is missing or has expired.                                                  | After getting a new token.        |
| `403 Forbidden`            | The token's project or roles do not allow this, or a quota is reached.                | No.                               |
| `404 Not Found`            | The resource does not exist, or your project cannot see it.                           | No.                               |
| `409 Conflict`             | The resource is in a state that does not allow this, or it changed since you read it. | After reading it again.           |
| `413 Content Too Large`    | The request body is too large.                                                        | No.                               |
| `422 Unprocessable Entity` | The body does not match what the operation expects.                                   | No. Fix the request.              |
| `429 Too Many Requests`    | The service is busy, or you sent too many requests.                                   | Yes, after `Retry-After` seconds. |
| `503 Service Unavailable`  | The service cannot answer right now.                                                  | Yes, with backoff.                |
| `504 Gateway Timeout`      | The request took too long.                                                            | Yes, with backoff.                |

## Retry safely

Wait as long as `Retry-After` says when a response includes it. Otherwise retry `503` and `504`
with exponential backoff, starting at about one second. Other `4xx` responses fail the same way
every time, so fix the request instead of retrying it.

## Request IDs

Responses carry a request ID header, such as `X-Openstack-Request-Id`. Include it when you contact
support about a request, so support can find the request quickly. Each API reference names the header
its service uses.

URL: https://docs.vexxhost.com/developer/api

---

# Agent setup

> Give AI assistants and coding agents this documentation as Markdown, through llms.txt indexes, per-page Markdown and OpenAPI schemas.

Every page on this site is also available as Markdown, which assistants and coding agents read
more reliably than HTML. This page lists where to find it.

## Markdown and indexes

| URL                                | Contents                                                  |
| ---------------------------------- | --------------------------------------------------------- |
| [`/llms.txt`](/llms.txt)           | An index of the site, with a link to each product's index |
| `/<product>/llms.txt`              | Every page of one product                                 |
| `/<product>/llms-full.txt`         | Every page of one product in one file                     |
| [`/llms-full.txt`](/llms-full.txt) | Every page of the site in one file                        |
| `<page>.md`                        | Any page as Markdown. Add `.md` to its URL.               |
| `/<product>/openapi.json`          | A product's OpenAPI 3.1 schema, also as `openapi.yaml`    |

Give an agent a product's `llms.txt` rather than the site-wide files. It is smaller, and it keeps
the agent on the product you are asking about.

On any page, **Copy Markdown** under the title copies that page for pasting into a chat, and
**Open** sends it to ChatGPT, Claude, or Cursor.

## Example

A prompt that points an agent at the right index:

```text
Read https://docs.vexxhost.com/llms.txt, then the index of the product I need, and write a Python
script that lists every resource in my project for that product.
```

URL: https://docs.vexxhost.com/developer/ai
