โ† OVHcloud Cost Optimizer

Setup guide

Everything you must provision before this skill can run โ€” the exact credentials, permissions, and where to click. Links go to the official consoles and docs, which stay current.

Some setupโ‰ˆ 30-45 minovh

Provision three separate credential sets on the OVHcloud account โ€” an OVHcloud APIv6 application key/secret/consumer key scoped to GET /me/bill* and GET /cloud/*, an OpenStack user (openrc.sh) with Compute/Volume/Image/Network operator roles on the Public Cloud project, and an S3 access key/secret pair for the project's Object Storage endpoint โ€” then install python-ovh, python-openstackclient and the AWS CLI locally.

You need to already have

  • An OVHcloud customer account (NIC handle) whose user holds Administrator rights on the account, or at minimum the Billing contact role โ€” /me/bill is only readable by an account contact with billing rights
  • At least one Public Cloud project in an ACTIVE state with a valid payment method (the 32-char hex project ID is the serviceName in every /cloud/project/{serviceName}/... call)
  • Rights on that Public Cloud project to create OpenStack users and to generate S3 credentials (Project administrator in the OVHcloud Control Panel)
  • Knowledge of which OVHcloud API region the account belongs to โ€” ovh-eu (eu.api.ovh.com), ovh-ca (ca.api.ovh.com) or ovh-us (api.us.ovhcloud.com). Application keys are NOT portable between endpoints
  • Python 3.8+ with pip, and the packages ovh, openstacksdk and python-openstackclient; AWS CLI v2 for the S3-compatible Object Storage calls
  • Instances must have been running long enough to have metrics โ€” /instance/{id}/monitoring returns nothing useful for freshly created or long-stopped instances, so the 30-day p95 logic degrades to in-guest measurement
  • Machine clock synchronised via NTP โ€” the OVHcloud APIv6 signature embeds a timestamp and drift over ~30s is rejected

How the pieces connect

Credentials this skill needs

Set these as environment variables. Never paste secrets into a chat or commit them.

VariableWhat it isWhere to get it
OVH_ENDPOINT
OVHcloud API endpoint alias
format: One of the literal strings: ovh-eu, ovh-ca, ovh-us
Determined by where the account was created; the same value must be chosen on the createToken page you use. EU accounts use ovh-eu (https://eu.api.ovh.com/1.0), Canada ovh-ca, US ovh-us.
OVH_APPLICATION_KEY
secret
OVHcloud API application key
format: 16-character alphanumeric string
https://api.ovh.com/createToken/ (EU) โ€” fill in Account ID, Password, Script name, Validity and the rights table, click Create keys; the response page shows Application Key, Application Secret and Consumer Key exactly once.
OVH_APPLICATION_SECRET
secret
OVHcloud API application secret
format: 32-character alphanumeric string
Shown on the same one-time result page as the application key at https://api.ovh.com/createToken/. It cannot be re-displayed; regenerate the token if lost.
OVH_CONSUMER_KEY
secret
OVHcloud API consumer key
format: 32-character alphanumeric string
Same one-time result page at https://api.ovh.com/createToken/. This key carries the method+path rules (e.g. GET /cloud/*); a consumer key created via POST /auth/credential must additionally be validated by logging in at the returned validationUrl.
OVH_CLOUD_PROJECT
Public Cloud project ID (serviceName)
format: 32-character lowercase hex string
OVHcloud Control Panel > Public Cloud > select the project > the ID is shown in the project selector and under Project Management > Project settings. Equivalently: GET /cloud/project returns the array of IDs.
OS_AUTH_URL
OpenStack Keystone auth URL
format: URL, e.g. https://auth.cloud.ovh.net/v3
Line in the openrc.sh downloaded from OVHcloud Control Panel > Public Cloud > Project Management > Users & Roles > the user's ... menu > Download OpenStack's RC file.
OS_USERNAME
OpenStack user name
format: user-<8 alphanumerics> generated by OVHcloud
OVHcloud Control Panel > Public Cloud > Project Management > Users & Roles > Add user (assign the roles listed under permissions). Also present in openrc.sh.
OS_PASSWORD
secret
OpenStack user password
format: Generated password string, displayed once at user creation
Shown once in the Users & Roles panel immediately after Add user; openrc.sh prompts for it at source time rather than storing it.
OS_PROJECT_ID
OpenStack project (tenant) ID
format: 32-character lowercase hex โ€” identical to OVH_CLOUD_PROJECT
Set by openrc.sh; same value as the Public Cloud project ID.
OS_REGION_NAME
OpenStack region
format: Region code, e.g. GRA11, SBG5, BHS5, WAW1, EU-WEST-PAR
openrc.sh defaults to one region; override per sweep with `openstack --os-region-name GRA11 ...` or re-export. Region list is visible in Control Panel > Public Cloud > Instances.
AWS_ACCESS_KEY_ID
secretoptional
S3 access key for OVHcloud Object Storage
format: 32-character alphanumeric string (distinct from the OVHcloud API application key)
`openstack ec2 credentials create` as the OpenStack user, or OVHcloud Control Panel > Public Cloud > Object Storage > Users tab > the user's ... menu > Generate S3 credentials. Required only for the object-storage tiering pass.
AWS_SECRET_ACCESS_KEY
secretoptional
S3 secret key for OVHcloud Object Storage
format: 64-character alphanumeric string
Returned together with the access key by `openstack ec2 credentials create` (field `secret`) or by Generate S3 credentials in the Control Panel.
AWS_DEFAULT_REGION
optional
S3 region for SigV4 signing
format: Lowercase region code matching the endpoint host, e.g. gra for https://s3.gra.io.cloud.ovh.net
Set manually; OVHcloud S3 requires the signing region to match the regional endpoint host or signature validation fails.
IDLE_CPU_P95
optional
Downsize threshold (p95 CPU %)
format: Number, defaults to 20
Optional tuning env var read by the skill's ovh_rightsize.py reference script; no provisioning needed.
HOT_CPU_P95
optional
Upsize threshold (p95 CPU %)
format: Number, defaults to 80
Optional tuning env var read by the skill's ovh_rightsize.py reference script; no provisioning needed.

Permissions to grant

GET /me/billAPI token ruleadmin consent required

Lists the last N invoice IDs so the audit works from the authoritative bill rather than the console summary.

โ†ณ No narrower form exists for the collection; pair it with GET /me/bill/* rather than granting GET /me/*.

GET /me/bill/*API token ruleadmin consent required

Reads priceWithoutTax and the per-invoice detail for the last three months to build the spend breakdown.

โ†ณ Use GET /me/bill/* only โ€” GET /me/* would also expose contacts, payment means and identity documents.

GET /cloud/projectAPI token ruleadmin consent required

Enumerates every Public Cloud project on the account, because bills aggregate across projects and auditing one project understates the total.

GET /cloud/project/*API token ruleadmin consent required

Covers usage/current, usage/forecast, usage/history, instance, flavor and instance monitoring reads in one rule.

โ†ณ Replace with the four explicit rules GET /cloud/project/*/usage/*, GET /cloud/project/*/instance, GET /cloud/project/*/instance/*/monitoring and GET /cloud/project/*/flavor to keep the token read-only and narrow.

PUT /cloud/project/*/instance/*/activeMonthlyBillingAPI token ruleadmin consent required

Switches an instance from hourly to monthly billing. Only grant this after a human has approved the commitment plan โ€” the switch is irreversible for the current month.

โ†ณ Omit entirely from the audit token; issue a second, short-validity token holding only this rule when the approved changes are executed.

POST /cloud/project/*/savingsPlan/*API token ruleadmin consent required

Runs the Savings Plan simulation so a commitment can be modelled against real usage before being purchased.

โ†ณ Grant the simulate path only; do not include the subscription/creation path in a read-only audit token.

Compute OperatorOpenStack (Keystone) project roleadmin consent required

Allows `openstack server list --long`, `openstack flavor list` and, once approved, `openstack server resize`.

โ†ณ Infrastructure Supervisor gives read-only visibility and is sufficient for the discovery sweep; add Compute Operator only for the approved resize window.

Volume OperatorOpenStack (Keystone) project roleadmin consent required

Allows `openstack volume list --status available` and `openstack volume snapshot list` to find unattached volumes and orphaned snapshots, which OVHcloud bills in full.

โ†ณ Read-only sweeps need only list rights; grant Volume Operator on a dedicated audit user rather than reusing an application user.

Image OperatorOpenStack (Keystone) project roleadmin consent required

Allows `openstack image list --private` to find stale private images that accrue storage charges.

Network OperatorOpenStack (Keystone) project roleadmin consent required

Allows `openstack floating ip list --status DOWN` to find unattached additional IPs.

ObjectStore operatorOpenStack (Keystone) project roleadmin consent required

Required for `openstack ec2 credentials create` and for the S3 user to list buckets and object metadata during the Cold Archive tiering analysis.

โ†ณ Attach a read-only S3 policy to the generated S3 user so the tiering pass cannot delete or re-tier objects; grant write only for the approved lifecycle-rule step.

Step by step

  1. 1

    Confirm the account can see invoices and identify the API region

    Sign in to the OVHcloud Control Panel. Open the account menu (top right) > Billing to confirm invoices are visible for this user โ€” if they are not, the user lacks the Billing contact role and GET /me/bill will return an empty list. Note whether the account lives in the EU, CA or US region; this fixes OVH_ENDPOINT.

    Open in the console / docs โ†—
  2. 2

    Create the OVHcloud APIv6 application key, secret and consumer key

    Go to the createToken page for your region (EU: https://api.ovh.com/createToken/, CA: https://ca.api.ovh.com/createToken/, US: https://api.us.ovhcloud.com/). Enter Account ID and Password, set Script name and Description, set Validity (choose a bounded validity, not Unlimited, for an audit). In the Rights table add one row per rule: GET /me/bill, GET /me/bill/*, GET /cloud/project, GET /cloud/project/*. Click Create keys and copy the three values immediately โ€” the secret is shown once.

    Open in the console / docs โ†—
  3. 3

    Store the API credentials outside the shell history

    Write them to ~/.ovh.conf (mode 600) in the form [default] endpoint=ovh-eu then an [ovh-eu] section with application_key, application_secret and consumer_key, or export OVH_ENDPOINT / OVH_APPLICATION_KEY / OVH_APPLICATION_SECRET / OVH_CONSUMER_KEY. python-ovh's ovh.Client() reads either source with no arguments. Never paste secrets into a chat transcript.

    Open in the console / docs โ†—
  4. 4

    Install the client libraries

    Run: pip install ovh openstacksdk python-openstackclient. Install AWS CLI v2 separately (it is used only as an S3-compatible client against the OVHcloud endpoint, no AWS account required).

    Open in the console / docs โ†—
  5. 5

    Capture every Public Cloud project ID

    Run the read-only call GET /cloud/project and record all returned IDs, then export the one under audit as OVH_CLOUD_PROJECT. Auditing a single project understates the bill; the account-level invoice covers all projects plus bare metal, domains and licences.

    Open in the console / docs โ†—
  6. 6

    Create a dedicated OpenStack audit user and assign roles

    OVHcloud Control Panel > Public Cloud > select the project > Project Management > Users & Roles > Add user. Give it a description such as 'finops-audit' and tick the roles Compute Operator, Volume Operator, Image Operator, Network Operator and ObjectStore operator (Infrastructure Supervisor alone is enough for a purely read-only pass). Copy the generated password shown once on creation.

    Open in the console / docs โ†—
  7. 7

    Download and source the OpenStack RC file

    On the same Users & Roles row, open the ... menu > Download OpenStack's RC file, choose the region, then run `source ~/openrc.sh` and enter the user password when prompted. Verify with `openstack token issue`. Horizon for the same project is at https://horizon.cloud.ovh.net/ if you prefer to confirm the roles visually.

    Open in the console / docs โ†—
  8. 8

    Generate S3 credentials for the object-storage pass

    With openrc.sh sourced run `openstack ec2 credentials create` and export the returned `access` as AWS_ACCESS_KEY_ID and `secret` as AWS_SECRET_ACCESS_KEY. Export AWS_DEFAULT_REGION to the lowercase region code (gra, sbg, bhs, waw, de, uk, sgp, syd) matching the endpoint host https://s3.<region>.io.cloud.ovh.net. These are distinct from the APIv6 keys.

    Open in the console / docs โ†—
  9. 9

    Verify all three credential sets before running the audit

    Run the verify command below for the APIv6 keys, `openstack server list --limit 1` for OpenStack, and `aws --endpoint-url https://s3.$AWS_DEFAULT_REGION.io.cloud.ovh.net s3 ls` for S3. All three must succeed; the skill's discovery phase is read-only and will otherwise fail mid-sweep with partial data.

    Open in the console / docs โ†—
  10. 10

    Pin the current list prices before any recommendation is written

    Open the Public Cloud pricing page for the correct currency and region and record the exact hourly and monthly rates for the flavors in use, plus block-storage class and object-storage/Cold Archive rates. Every figure in the skill's cost model is an approximation and must be replaced with a verified current price.

    Open in the console / docs โ†—

Check it worked

python3 -c "import ovh; c=ovh.Client(); print('me:', c.get('/me')['nichandle']); print('projects:', c.get('/cloud/project')); print('bills:', c.get('/me/bill')[:3])"

If you hit an error

ovh.exceptions.NotGrantedCall: This call has not been granted

Cause: The consumer key's rules do not cover the method+path being called โ€” typically GET /cloud/* was granted but GET /me/bill was not, or a rule was written as /cloud/project without the trailing /*.

Fix: Recreate the token at https://api.ovh.com/createToken/ with one explicit row per rule: GET /me/bill, GET /me/bill/*, GET /cloud/project, GET /cloud/project/*. Rules cannot be edited after creation.

ovh.exceptions.InvalidKey: Invalid application key

Cause: The application key was created on a different regional endpoint than OVH_ENDPOINT points at (for example an ovh-eu key used against ovh-ca), or a stray whitespace/newline was captured when copying.

Fix: Match OVH_ENDPOINT to the createToken host that issued the key and re-copy the value without surrounding whitespace.

ovh.exceptions.InvalidCredential: This credential is not valid

Cause: A consumer key obtained via POST /auth/credential was never validated by a human at the returned validationUrl, or the token's validity period has expired.

Fix: Open the validationUrl and log in to activate the consumer key, or issue a new token via the createToken page which returns an already-validated consumer key.

Invalid signature

Cause: The X-Ovh-Signature is computed over a timestamp; a machine clock more than ~30 seconds off the OVHcloud server time invalidates every request. A wrong application secret produces the same message.

Fix: Sync the clock via NTP, then re-check OVH_APPLICATION_SECRET. python-ovh fetches server time from /auth/time automatically, so a proxy that blocks that call also triggers this.

Missing value auth-url required for auth plugin password

Cause: openrc.sh was not sourced in the current shell (a new Bash tool call or subshell resets the environment), so OS_AUTH_URL is unset.

Fix: Run `source ~/openrc.sh` in the same shell as the openstack command, or pass --os-auth-url/--os-username/--os-project-id explicitly.

The request you have made requires authentication. (HTTP 401) (Request-ID: req-...)

Cause: Wrong OS_PASSWORD, an OpenStack user deleted or regenerated in the Control Panel, or the password was typed into the wrong region's openrc.

Fix: Regenerate the password from Public Cloud > Project Management > Users & Roles and re-source openrc.sh.

You are not authorized to perform the requested action. (HTTP 403)

Cause: The OpenStack user is missing the role for that resource family โ€” e.g. listing volumes without Volume Operator, or floating IPs without Network Operator.

Fix: Add the missing role on the user in Public Cloud > Project Management > Users & Roles; role changes take effect on the next token issue, so re-source openrc.sh.

An error occurred (SignatureDoesNotMatch) when calling the ListObjectsV2 operation

Cause: AWS_DEFAULT_REGION does not match the regional S3 endpoint host, so SigV4 signs for the wrong region scope.

Fix: Set AWS_DEFAULT_REGION to the lowercase region in the endpoint (gra for https://s3.gra.io.cloud.ovh.net) and always pass --endpoint-url.

An error occurred (InvalidAccessKeyId) when calling the ListObjectsV2 operation

Cause: The OVHcloud APIv6 application key was exported as AWS_ACCESS_KEY_ID. The two credential systems are unrelated.

Fix: Generate S3 credentials with `openstack ec2 credentials create` (or Generate S3 credentials in the Object Storage > Users tab) and use those values.

An error occurred (AccessDenied) when calling the PutBucketLifecycleConfiguration operation

Cause: The S3 user holds a read-only policy, which is correct for the discovery pass but insufficient to write the AbortIncompleteMultipartUpload lifecycle rule.

Fix: Only after human approval, grant the S3 user write rights on the specific bucket, apply the rule, then revert the policy.

Official documentation: https://api.ovh.com/ โ†—

API operations this skill uses (20)

Every call the skill makes, linked to its official reference page.

OperationCallPermissionRef
List invoices
Pulls the last six invoice IDs so the audit starts from the authoritative account-level bill instead of the Public Cloud console summary.
GET /me/billGET /me/billdocs โ†—
Get invoice detail
Reads priceWithoutTax per invoice across three months to establish the run rate and expose seasonality that a single month hides.
GET /me/bill/{billId}GET /me/bill/*docs โ†—
List Public Cloud projects
Enumerates every project on the account, because bills aggregate across projects and auditing one project understates the total.
GET /cloud/projectGET /cloud/projectdocs โ†—
Get current-period usage
Returns hourlyUsage, monthlyUsage and resourcesUsage split by instance, volume, storage, snapshot and bandwidth โ€” the source for the per-category spend breakdown and for per-instance EUR attribution in ovh_rightsize.py.
GET /cloud/project/{serviceName}/usage/currentGET /cloud/project/*docs โ†—
Get month-end usage forecast
Projects the current period to month end so savings are quoted against a full month rather than a partial one.
GET /cloud/project/{serviceName}/usage/forecastGET /cloud/project/*docs โ†—
Get historical usage
Supplies the compute hours actually run per instance, which drives the hourly vs monthly crossover (~55% / ~80% of the month) and sizes a Savings Plan to the three-month trough.
GET /cloud/project/{serviceName}/usage/historyGET /cloud/project/*docs โ†—
List instances in a project
Returns id, name, region, status and flavorId for every instance; status != ACTIVE flags the SHUTOFF-but-still-billed finding that is usually the largest single win.
GET /cloud/project/{serviceName}/instanceGET /cloud/project/*docs โ†—
List flavors per region
Maps flavorId to name, vcpus, ram and osType so the in-family sibling (b3-32 -> b3-16) and cross-family moves (b3 -> r3 / c3) resolve to flavors that actually exist in that region.
GET /cloud/project/{serviceName}/flavor?region={region}GET /cloud/project/*docs โ†—
Get instance monitoring series
Provides the 30-day cpu:used and mem:used series from which p95 is computed; right-sizing decisions are made on p95, never on an instantaneous or peak reading.
GET /cloud/project/{serviceName}/instance/{instanceId}/monitoring?period=lastmonth&type=cpu:usedGET /cloud/project/*docs โ†—
Enable monthly billing on an instance
Executes the approved hourly-to-monthly switch for instances running above ~80% of the month. Irreversible for the current period and only run after explicit human approval.
PUT /cloud/project/{serviceName}/instance/{instanceId}/activeMonthlyBillingPUT /cloud/project/*/instance/*/activeMonthlyBillingdocs โ†—
Simulate a Savings Plan
Models a 1- or 3-year per-flavor-family commitment against real usage before purchase; the committed quantity is set from the three-month trough, since unused commitment is pure loss.
POST /cloud/project/{serviceName}/savingsPlan/simulatePOST /cloud/project/*/savingsPlan/*docs โ†—
List servers with status and flavor
Cross-checks the API instance list and isolates non-ACTIVE servers, which OVHcloud Public Cloud bills at the full flavor price.
CLI openstack server list --long -f value -c Name -c Status -c FlavorCompute Operator (or Infrastructure Supervisor for read-only)docs โ†—
List unattached block volumes
Finds detached volumes, which are billed in full; also feeds the block-storage class downgrade analysis (Gen2 -> High Speed -> Classic).
CLI openstack volume list --status available -f value -c ID -c Name -c SizeVolume Operatordocs โ†—
List volume snapshots
Surfaces orphaned snapshots (long-tail leakage at roughly EUR 0.04/GB/month) and verifies a snapshot exists before any volume or instance deletion.
CLI openstack volume snapshot list -f value -c ID -c Name -c SizeVolume Operatordocs โ†—
List private images
Finds stale private images that keep accruing storage charges after the instances they were captured from are gone.
CLI openstack image list --private -f value -c ID -c NameImage Operatordocs โ†—
List unattached floating IPs
Identifies additional IPs billed a couple of EUR/month each while attached to nothing โ€” small individually, persistent for years.
CLI openstack floating ip list --status DOWN -f value -c IDNetwork Operatordocs โ†—
Resize an instance to the target flavor
Applies an approved right-sizing. Disruptive: snapshot first, batch all resizes into one maintenance window, and run only after explicit approval.
CLI openstack server resize --flavor <new-flavor> <server> && openstack server resize confirm <server>Compute Operatordocs โ†—
List cold objects in an Object Storage bucket
Sizes the Cold Archive candidate set by last-modified date; candidates are then validated against minimum storage duration, restore fee and hour-scale unseal latency before anything is moved.
CLI aws --endpoint-url https://s3.<region>.io.cloud.ovh.net s3api list-objects-v2 --bucket <bucket> --query 'Contents[?LastModified<=`YYYY-MM-DD`].Size'ObjectStore operator + S3 credentials with read access to the bucketdocs โ†—
Set a lifecycle rule aborting incomplete multipart uploads
Applies the AbortIncompleteMultipartUpload rule at 7 days so abandoned multipart parts stop accumulating invisible storage charges. Mutating โ€” approval required.
CLI aws --endpoint-url https://s3.<region>.io.cloud.ovh.net s3api put-bucket-lifecycle-configuration --bucket <bucket> --lifecycle-configuration file://lifecycle.jsonS3 credentials with write access to the bucket (approval-gated)docs โ†—
Look up a hyperscaler instance type for like-for-like comparison
Confirms vCPU, RAM and whether CPU is guaranteed or burstable before comparing against an OVHcloud flavor, so the comparison is not built on guessed specs. Equivalents: `gcloud compute machine-types describe`, `az vm list-sizes`.
CLI aws ec2 describe-instance-types --instance-types m6i.2xlargeAWS credentials with ec2:DescribeInstanceTypes (incumbent account, read-only)docs โ†—