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.
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.
| Variable | What it is | Where 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_KEYsecret | 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_SECRETsecret | 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_KEYsecret | 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_PASSWORDsecret | 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_IDsecretoptional | 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_KEYsecretoptional | 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_REGIONoptional | 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_P95optional | 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_P95optional | 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 requiredLists 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 requiredReads 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 requiredEnumerates 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 requiredCovers 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 requiredSwitches 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 requiredRuns 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 requiredAllows `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 requiredAllows `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 requiredAllows `openstack image list --private` to find stale private images that accrue storage charges.
Network OperatorOpenStack (Keystone) project roleadmin consent requiredAllows `openstack floating ip list --status DOWN` to find unattached additional IPs.
ObjectStore operatorOpenStack (Keystone) project roleadmin consent requiredRequired 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
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
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
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
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
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
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
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
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
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
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 grantedCause: 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 keyCause: 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 validCause: 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 signatureCause: 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 passwordCause: 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 operationCause: 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 operationCause: 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 operationCause: 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.
| Operation | Call | Permission | Ref |
|---|---|---|---|
| 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/bill | GET /me/bill | docs โ |
| 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/project | GET /cloud/project | docs โ |
| 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/current | GET /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/forecast | GET /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/history | GET /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}/instance | GET /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:used | GET /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}/activeMonthlyBilling | PUT /cloud/project/*/instance/*/activeMonthlyBilling | docs โ |
| 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/simulate | POST /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 Flavor | Compute 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 Size | Volume Operator | docs โ |
| 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 Size | Volume Operator | docs โ |
| 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 Name | Image Operator | docs โ |
| 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 ID | Network Operator | docs โ |
| 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 Operator | docs โ |
| 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 bucket | docs โ |
| 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.json | S3 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.2xlarge | AWS credentials with ec2:DescribeInstanceTypes (incumbent account, read-only) | docs โ |