id: ovhcloud name: OVHcloud version: 1.2.6 description: "OVHcloud (API v1) — cloud account and infrastructure visibility, plus VoIP on-call routing: read the account, list public cloud projects, list/read dedicated servers, and hold Riposte's on-call ladder in a telephony call queue so a client number reaches whoever is on duty without any channel on your own PBX. Signed application-key authentication; stdlib-only, no extra Python dependencies. (French vendor.)" changelog: "1.2.6 — The billing-account check is advisory in every case, not only when it errors: OVH's IAM answers an EMPTY LIST rather than a refusal, and a policy can grant a resource without granting the route that enumerates it — so \"not in the list\" was failing instances whose account-scoped routes work. When nothing is reachable anywhere the message now names the IAM policy as the cause and says what to grant. | 1.2.5 — Stop rewriting the API path for a service account. 1.2.0 turned /1.0 into /v1 on the strength of a documentation example, and OVH's own console issues Bearer requests against /1.0 — while /v1 answers 500 with an empty body for a token on the telephony routes. The endpoint is now used exactly as configured, for both authentication schemes. | 1.2.4 — The billing-account listing is no longer fatal. On a service account it can answer 500 with an empty body while every account-scoped route underneath works — so the diagnostic step was failing the test it exists to explain, and hiding the answer behind its own failure. When neither product can be listed either, the error now names what each HTTP code means and points at the application-key fallback. | 1.2.3 — The Test button now WALKS to the queue one step at a time and reports what it saw at each: the billing accounts the credentials can reach, the services each product holds, the queues in the one chosen. A bare 404 from this API means a wrong account, a wrong service name, the other product, or a number nobody configured — four jobs, one message — and none of them could be told apart from Riposte. It also answers its own version, because when a test fails identically twice the first question is whether the new code is the code that ran. | 1.2.2 — Find the call queue under either product. OVH files a queue under easyHunting or under ovhPabx depending on the number's offer, and the control panel says \"File d'appels\" for both — so assuming the first answered 404 \"This service does not exist\", which sounds like a wrong number and is in fact a wrong product. The product is now discovered once per run, and a service found under neither says which names DO exist. | 1.2.1 — The Test button now probes what the instance is FOR: with telephony coordinates filled in it reads the call queue, which proves the credentials, the IAM policy, the billing account, the service and the queue at once. It used to read /me unconditionally, so a service account correctly narrowed to /telephony failed its own test with a 403 on account:apiovh:me/get — credentials that were perfect, reported as broken. | 1.2.0 — Accept a SERVICE ACCOUNT (OAuth2 client credentials) as well as the historical application keys, because that is what the control panel offers on accounts where application keys are not available. Which scheme is used is decided by what you filled in, not by a mode selector, and an existing instance keeps working untouched. The API base is corrected to /v1 for a service account, since a Bearer token sent to the legacy /1.0 spelling fails in a way that reads as bad credentials. | 1.1.0 — On-call routing: set_oncall_ladder / get_oncall_ladder / list_calls, backed by a VoIP call queue (easyHunting). Riposte publishes the ordered on-call numbers and the carrier rings them in that order, so an inbound client call never crosses your own trunk and spends no simultaneous call on it. Needs the three telephony_* config fields; every other command is unchanged. | 1.0.0 — Initial release: get account, list cloud projects, list/get dedicated servers." category: cloud # Per-instance configuration. Requests are signed (SHA-1) with the application # key/secret + consumer key. endpoint selects the region (eu/ca/us). config_schema: properties: endpoint: type: string description: "OVH API endpoint, copied from the API console — used exactly as typed, for both authentication schemes" default: "https://eu.api.ovh.com/1.0" # ── credentials: fill in ONE of the two sets ───────────────────────── # # A service account (OAuth2 client credentials) is what the control panel # offers on accounts where application keys are not available. Filling in # client_id AND client_secret selects it; anything else falls back to the # historical signed scheme below. client_id: type: string description: "Service account: client ID (OAuth2 client credentials)" client_secret: type: string description: "Service account: client secret" x-soar-sensitive: true oauth_token_url: type: string description: "Service account: token endpoint, only if it differs from the region default (EU: https://www.ovh.com/auth/oauth2/token)" application_key: type: string description: "Historical scheme: application key" application_secret: type: string description: "Historical scheme: application secret" x-soar-sensitive: true consumer_key: type: string description: "Historical scheme: consumer key" x-soar-sensitive: true # ── VoIP on-call routing ───────────────────────────────────────────── # # Only the three on-call commands read these. Leave them empty and this # integration is exactly what it was before: an OVHcloud reader. telephony_billing_account: type: string description: "Telephony billing account, e.g. fr-xxxxx-1 (Telecom > VoIP in the control panel)" telephony_service: type: string description: "The call queue's service name — the number it is attached to, in 0033xxxxxxxxx form. Works under either easyHunting or ovhPabx; the product is discovered." telephony_queue_id: type: string description: "Queue id, only needed when the service holds more than one queue" # NOTHING IS REQUIRED HERE, and that is not laxity: this schema cannot express # "one set or the other", and marking the historical three required would refuse # every service-account instance. The scripts refuse an instance with neither, # naming both sets — an error an operator can act on, where a form that will not # submit is one they can only fight. required: [] commands: - id: get_account name: ovh-get-account description: "Get the current account (nichandle)." risk: read inputs_schema: properties: {} required: [] outputs_schema: { properties: {} } - id: list_cloud_projects name: ovh-list-cloud-projects description: "List Public Cloud project IDs." risk: read inputs_schema: properties: {} required: [] outputs_schema: { properties: {} } - id: list_dedicated_servers name: ovh-list-dedicated-servers description: "List dedicated server names." risk: read inputs_schema: properties: {} required: [] outputs_schema: { properties: {} } - id: get_dedicated_server name: ovh-get-dedicated-server description: "Get a dedicated server's details." risk: read inputs_schema: properties: server_name: { type: string, description: "Dedicated server name" } required: [server_name] outputs_schema: { properties: {} } - id: test_connection name: ovh-test-connection description: "Verify the signed credentials via /me (used by the Test button)." risk: read inputs_schema: properties: {} required: [] outputs_schema: { properties: {} } # ── On-call routing ─────────────────────────────────────────────────────── # # THE THREE COMMANDS RIPOSTE'S PAGER LOOKS FOR. An integration becomes an # on-call router by declaring set_oncall_ladder and nothing else: there is no # registry, no vendor list, and the other two are optional. # # What they act on is a VoIP CALL QUEUE (easyHunting): its agents are the rungs, # their position is the order, and the queue rings them one after another. The # call is answered by the carrier, so it never reaches your PBX and spends none # of its simultaneous calls — which is the whole reason to route an on-call # number this way rather than through Asterisk. # # WHAT THIS CANNOT DO, said here because it decides whether the trade is worth # it: a carrier's queue cannot ask the analyst to press a key before connecting # the client. A mobile's voicemail answering therefore ends the search as though # a person had picked up. Keeping ring_seconds under 20 is the only mitigation. - id: set_oncall_ladder name: ovh-set-oncall-ladder description: "Publish the ordered on-call numbers to the call queue. Agents are created as needed, ordered by position, and anybody no longer on the ladder is logged out — never deleted." # DESTRUCTIVE rather than safe_write, and deliberately above its blast radius: # nothing is destroyed, but this decides which phone rings at 03:00. Riposte's # pager calls it directly; the tier is what stops an AI agent rerouting an # on-call number unattended. risk: destructive inputs_schema: properties: numbers: type: array description: "Ordered numbers to ring (E.164 or 00-prefixed). Rung 1 first." ring_seconds: type: number description: "How long each rung rings before the next one, 5 to 30" required: [numbers] outputs_schema: { properties: {} } - id: get_oncall_ladder name: ovh-get-oncall-ladder description: "Read back the ladder the queue actually holds, in its order. Reads only; places no call." risk: read inputs_schema: properties: {} required: [] outputs_schema: { properties: {} } - id: list_calls name: ovh-list-oncall-calls description: "Recent inbound calls on the on-call number, for the pager journal." risk: read inputs_schema: properties: since: type: string description: "ISO-8601 instant to read from (default: 24 hours ago)" limit: type: number description: "Maximum calls to detail (default 50, max 200)" required: [] outputs_schema: { properties: {} }