ovh-api
A generated Ruby transport gem for the entire OVH REST API (all 70 top-level resources), produced from OVH's published schemas.
ovh-api is the thin, machine-generated HTTP layer. Business ergonomics and quirk
absorption belong in a hand-written wrapper on top (ovh-client, à la dolibarr-client
over dolibarr-api). OVH's request-signing auth is not baked in here — it is injected
as a middleware through the generator's auth seam (see Authentication below).
How it is built
OVH does not publish OpenAPI. Its API is described in legacy Swagger 1.2
(per-resource API Declarations, no swaggerVersion, no security definitions), which
openapi-generator cannot ingest. The pipeline bridges that gap:
versions/raw/*.json pinned OVH 1.2 declarations (70 resources)
└─ scripts/convert.mjs
├─ union-merge colliding models OVH redefines shared types (service.*, order.*, …)
│ per product; enums → union of values, structs →
│ union of properties (all optional)
├─ swagger-converter 1.2 → 2.0, merges all 70 resources into one doc
├─ 3 structural fixups
│ • dangling $refs OVH scalar types (password, ipBlock, …) declared
│ as undefined models; array aliases (Foo[]); maps
│ • multi-body params 1.2 emits one `body` param per POST field; 2.0
│ allows only one → merged into a single object
│ • missing path params `{id}` in the template but undeclared (e.g.
│ /telephony …/{queueId}/agent)
└─ swagger2openapi 2.0 → 3.0
└─ versions/ovh.oas3.json → fed to openapi-generator (-g ruby-idiomatic)
The last full run: 70 resources → 4728 paths, 5268 schemas, OpenAPI 3.0.0,
openapi-generator validate clean (0 errors; only harmless "unused model" warnings).
Two models remain mapped to a free-form object (lossy but harmless): T (a generic
placeholder) and coreTypes.AccountId:string.
Commands (mise)
mise run fetch # refresh versions/raw/*.json from api.ovh.com (overwrites pinned schemas)
mise run convert # versions/raw → versions/ovh.oas3.json
mise run generate # generate the gem into lib/** (-g ruby-idiomatic)
mise run build # convert + generate + format
mise run dev:spec # run the generated spec suite
versions/raw/*.json are committed so the build is reproducible without hitting OVH.
Run mise run fetch only to deliberately refresh the pinned schemas.
Generator dependency
ruby-idiomatic is not in any published openapi-generator release — it lives in a local
fork. mise run generate targets the fork's built CLI jar (OVH_FORK var in mise.toml).
The jar is authoritative: rebuild it after any template or Java change to the generator so
the embedded templates (including the auth-signing seam) are current:
cd "$OVH_FORK" && ./mvnw -pl modules/openapi-generator-cli -am package -DskipTests
Authentication (request signing)
OVH signs each request: headers X-Ovh-Application, X-Ovh-Consumer, X-Ovh-Timestamp,
and X-Ovh-Signature = $1$ + SHA1(`app_secret + consumer_key + METHOD + full_url + body
- timestamp
). This is a per-request signature, **not** an OpenAPI security scheme, so it is injected through theruby-idiomatic` middleware seam rather than generated:
require "digest/sha1"
class OvhSignature < Faraday::Middleware
def initialize(app, app_key:, app_secret:, consumer_key:)
super(app)
@app_key, @app_secret, @consumer_key = app_key, app_secret, consumer_key
end
def on_request(env)
ts = Time.now.to_i.to_s
sig = "$1$" + Digest::SHA1.hexdigest(
[@app_secret, @consumer_key, env.method.to_s.upcase, env.url.to_s, env.body.to_s, ts].join("+"))
env.request_headers.merge!(
"X-Ovh-Application" => @app_key, "X-Ovh-Consumer" => @consumer_key,
"X-Ovh-Timestamp" => ts, "X-Ovh-Signature" => sig)
end
end
client = Ovh::Api::Client.new do |config|
config.use(OvhSignature, app_key: ENV["OVH_APP_KEY"], app_secret: ENV["OVH_APP_SECRET"],
consumer_key: ENV["OVH_CONSUMER_KEY"])
end
Layout
versions/raw/ pinned OVH 1.2 schemas (source of truth, committed)
versions/resources.txt the 70 top-level resource paths
scripts/convert.mjs conversion pipeline (1.2 → OpenAPI 3.0)
mise.toml fetch / convert / generate / build tasks
lib/ generated gem — DO NOT edit by hand