1.2.0 rewrote /1.0 to /v1 whenever a service account was in use, reasoning from a
documentation example that showed OAuth2 against a /v1 path. It was wrong, and it
was the cause of the 404s and the 500 that followed: OVH's own API console issues
Bearer requests against /1.0, while /v1 — which exists, and answers 401
unauthenticated like any live route — answers 500 with an empty body for a token
on the telephony routes. A failure with no message at all, produced by a helpful
rewrite nobody asked for.
The endpoint is now whatever was typed, for both authentication schemes. The
config field says where to copy it from.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TN1R4EnNFUV2Xwtd9q98tj
GET /telephony — the list of billing accounts — answers 500 with an empty body on
at least one service account. Nothing in this integration needs that list: it was
added as a diagnostic, and it promptly failed the very test it exists to explain,
hiding whatever the account-scoped routes had to say behind its own failure. It is
now recorded and stepped over.
When neither product can be listed either, the message names what each code means
rather than repeating the status: 403 is an IAM policy missing the telephony
actions, 404 is a billing account that does not exist under these credentials, and
500 on every telephony route means this service account cannot reach the telephony
API at all — for which the way round is an application key from
api.ovh.com/createToken, which this integration already accepts.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TN1R4EnNFUV2Xwtd9q98tj
This endpoint answers a bare 404 — "This service does not exist" — for a wrong
billing account, a wrong service name, a queue filed under the other product, and
a number nobody has configured yet. Four different jobs, one message, and nothing
on the Riposte screen could tell them apart. Two rounds went to guessing which one
it was.
The probe now walks there one step at a time and reports what it saw at each: the
billing accounts these credentials can reach, the services each product holds, the
queues in the chosen one. A failure names the step and lists the values that do
exist, which is what somebody can act on.
It also answers its own VERSION. When a test fails identically twice, the first
question is whether the new code is the code that ran, and nothing on the screen
could answer it.
And a failed diagnosis now exits non-zero with an "error" key. Returning ok:false
on exit 0 would have been worse than the 404 it replaces: the screen would call
the instance healthy while naming the step where it is not.
Verified end to end through the real entry point for six shapes — success under
either product, unreachable account, wrong account, number that is not a queue,
service with no queue, and a listing the IAM policy refuses.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TN1R4EnNFUV2Xwtd9q98tj
A queue reached 404 "This service does not exist" on an account where the number
was plainly configured as one. The number was right: the PRODUCT was wrong. OVH
files a call queue under easyHunting or under ovhPabx depending on the offer the
number carries, the hunting routes underneath are identical either way, and the
control panel says "File d'appels" for both — so there is nothing an operator can
look at to tell which one they have.
It is now discovered once per run rather than assumed, and a service found under
neither product says which names DO exist under each. That turns the least
helpful error this API produces — one that reads as a wrong number — into a list
somebody can copy the right value out of.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TN1R4EnNFUV2Xwtd9q98tj
An instance created for on-call routing has no business reading the account, and a
service account narrowed to /telephony — which is exactly how it should be
narrowed — answers 403 "User not granted for this request" on
account:apiovh:me/get. The credentials were perfect and the Test button said they
were not, which is the worst thing a test can do: it sends somebody off to
regenerate a secret that was never wrong.
With telephony coordinates configured the probe now reads the service's call
queues instead. That is worth more than /me ever was: one call proves the
credentials, the IAM policy, the billing account, the service AND the queue. With
no coordinates it still reads the account, which is the right probe for an
instance used to look at servers.
A service found holding no queue is reported as a SUCCESS with a sentence saying
so. It is not a credential failure, and confusing the two is the difference
between "go and create a queue" and an evening spent on authentication.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TN1R4EnNFUV2Xwtd9q98tj
The control panel does not offer application keys on every account — some can only
create a SERVICE ACCOUNT, which authenticates with OAuth2 client credentials. An
instance built that way could not talk to the API at all, which is a hard stop for
anybody whose account is in that shape.
Both schemes are now supported, and which one is used is decided by WHAT WAS
FILLED IN rather than by a mode selector: a client_id and a client_secret mean the
service account, anything else falls back to the signed scheme. One less field to
get wrong, and every existing instance keeps working untouched.
The API base is corrected to /v1 for a service account. The same routes answer
under /1.0 and /v1, but a Bearer token sent to the legacy spelling fails in a way
that reads as "bad credentials" — which sends somebody off to regenerate a
perfectly good secret. An operator should not have to know that the spelling and
the credential go together.
Nothing in config_schema is required any more. The schema cannot express "one set
or the other", and keeping the historical three required would refuse every
service-account instance; the scripts refuse an instance with neither and name
both sets, which is an error somebody can act on — where a form that will not
submit is one they can only fight.
Two things the shared block now caches for the life of the process, both of which
matter on the ladder path that runs every time the rota turns: the access token
(a token request in front of each of a dozen calls would triple the traffic) and
the clock offset against OVH's time (it was a round trip per signed request).
Verified offline for both schemes: the base URL each one talks to, the headers
that land on the wire, the single token request, and the refusal when neither set
is present.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TN1R4EnNFUV2Xwtd9q98tj
Riposte's pager can hand its on-call ladder to a marketplace instance, so that a
number published to clients rings whoever is on duty without the call ever
crossing the SOC's own trunk — and without spending one of its simultaneous
calls, which are sold in blocks and are the reason the PBX-side mode is too
expensive for a small deployment.
Three commands, and they are a contract rather than this integration's own
vocabulary: set_oncall_ladder (ordered numbers + ring_seconds), get_oncall_ladder
(what the carrier ACTUALLY holds, so a comparison can be honest) and list_calls
(the journal, after the fact). The pager offers whichever instances declare the
first one; the README now documents the contract for whoever writes the next
router.
Extends the existing ovhcloud integration rather than adding a second one: it
already carries the signed-request authentication, and making an operator enter
the same application key twice would serve nobody. Three telephony_* config
fields are added; every existing command is untouched, and an instance that
leaves them empty is exactly the OVHcloud reader it was before.
set_oncall_ladder RECONCILES rather than rebuilds. Agents are created when a
number has none, reordered by position, and anybody who drops off the ladder is
LOGGED OUT and removed from the queue — never deleted, because deleting throws
away whatever else the operator configured on that agent and they are on the rota
again next week. It also enforces the sequential strategy: publishing an ordered
list into a queue set to ring everybody at once is a lie that is invisible from
Riposte.
Two details that would each have cost an evening: OVH accepts no "+" in a
phoneNumber field, so E.164 is converted to the 00-prefixed form both ways; and a
service holding several queues is REFUSED rather than guessed, because guessing
publishes the on-call ladder into whichever queue the API happened to list first.
Verified against a stubbed API for the first publication and for a rota handover.
The signature, the error codes and the exact number format are only proved by the
first real call.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TN1R4EnNFUV2Xwtd9q98tj
OVH API v1, 5 commands: get account, list cloud projects, list/get dedicated
servers. SHA-1 signed application-key auth, stdlib-only. py_compile clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>